osc-lib-1.9.0/0000775000175100017510000000000013227377613013110 5ustar zuulzuul00000000000000osc-lib-1.9.0/.zuul.yaml0000666000175100017510000000114413227377263015054 0ustar zuulzuul00000000000000- project: name: openstack/osc-lib check: jobs: - osc-functional-devstack: required-projects: - openstack/python-openstackclient - osc-functional-devstack-tips: voting: false # The functional-tips job only tests the latest and shouldn't be run # on the stable branches branches: ^(?!stable) required-projects: - openstack/python-openstackclient gate: jobs: - osc-functional-devstack: required-projects: - openstack/python-openstackclient osc-lib-1.9.0/PKG-INFO0000664000175100017510000000726013227377613014212 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: osc-lib Version: 1.9.0 Summary: OpenStackClient Library Home-page: https://docs.openstack.org/osc-lib/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======= osc-lib ======= .. image:: https://img.shields.io/pypi/v/osc-lib.svg :target: https://pypi.python.org/pypi/osc-lib/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/osc-lib.svg :target: https://pypi.python.org/pypi/osc-lib/ :alt: Downloads OpenStackClient (aka OSC) is a command-line client for OpenStack. osc-lib is a package of common support modules for writing OSC plugins. * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - part of OpenStackClient * `Bugs`_ - issue tracking * `Source`_ * `Developer` - getting started as a developer * `Contributing` - contributing code * `Testing` - testing code * IRC: #openstack-sdks on Freenode (irc.freenode.net) * License: Apache 2.0 .. _PyPi: https://pypi.python.org/pypi/osc-lib .. _Online Documentation: http://docs.openstack.org/osc-lib/latest/ .. _Launchpad project: https://launchpad.net/python-openstackclient .. _Bugs: https://bugs.launchpad.net/python-openstackclient .. _Source: https://git.openstack.org/cgit/openstack/osc-lib .. _Developer: http://docs.openstack.org/project-team-guide/project-setup/python.html .. _Contributing: http://docs.openstack.org/infra/manual/developers.html .. _Testing: http://docs.openstack.org/osc-lib/latest/contributor/#testing Getting Started =============== osc-lib can be installed from PyPI using pip:: pip install osc-lib Transition From OpenStackclient =============================== This library was extracted from the main OSC repo after the OSC 2.4.0 release. The following are the changes to imports that will cover the majority of transition to using osc-lib: * openstackclient.api.api -> osc_lib.api.api * openstackclient.api.auth -> osc_lib.api.auth * openstackclient.api.utils -> osc_lib.api.utils * openstackclient.common.command -> osc_lib.command.command * openstackclient.common.commandmanager -> osc_lib.command.commandmanager * openstackclient.common.exceptions -> osc_lib.exceptions * openstackclient.common.logs -> osc_lib.logs * openstackclient.common.parseractions -> osc_lib.cli.parseractions * openstackclient.common.session -> osc_lib.session * openstackclient.common.utils -> osc_lib.utils * openstackclient.i18n -> osc_lib.i18n * openstackclient.shell -> osc_lib.shell Also, some of the test fixtures and modules may be used: * openstackclient.tests.fakes -> osc_lib.tests.fakes * openstackclient.tests.utils -> osc_lib.tests.utils Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 osc-lib-1.9.0/osc_lib/0000775000175100017510000000000013227377613014522 5ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/i18n.py0000666000175100017510000000141713227377263015661 0ustar zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # # 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 oslo_i18n _translators = oslo_i18n.TranslatorFactory(domain='osc_lib') # The primary translation function using the well-known name "_" _ = _translators.primary osc-lib-1.9.0/osc_lib/cli/0000775000175100017510000000000013227377613015271 5ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/cli/client_config.py0000666000175100017510000002162613227377263020460 0ustar zuulzuul00000000000000# 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. # """OpenStackConfig subclass for argument compatibility""" import logging try: from openstack.config import exceptions as occ_exceptions except ImportError: from os_client_config import exceptions as occ_exceptions try: from openstack.config import loader as config except ImportError: from os_client_config import config from oslo_utils import strutils import six LOG = logging.getLogger(__name__) # Sublcass OpenStackConfig in order to munge config values # before auth plugins are loaded class OSC_Config(config.OpenStackConfig): def _auth_select_default_plugin(self, config): """Select a default plugin based on supplied arguments Migrated from auth.select_auth_plugin() """ identity_version = str(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 (str(config.get('identity_api_version', '')).startswith('2') and config.get('auth_type').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 def _auth_default_domain(self, config): """Set a default domain from available arguments Migrated from clientmanager.setup_auth() """ identity_version = str(config.get('identity_api_version', '')) auth_type = config.get('auth_type', None) # TODO(mordred): This is a usability improvement that's broadly useful # We should port it back up into os-client-config. default_domain = config.get('default_domain', None) if (identity_version == '3' and not auth_type.startswith('v2') and default_domain): # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is # 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') ): config['auth']['project_domain_id'] = default_domain # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present, # then do not change the behaviour. Otherwise, set the # USER_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. # NOTE(aloga): this should only be set if there is a username. # TODO(dtroyer): Move this to os-client-config after the plugin has # been loaded so we can check directly if the options are accepted. if ( auth_type in ("password", "v3password", "v3totp") and not config['auth'].get('user_domain_id') and not config['auth'].get('user_domain_name') ): 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) if LOG.isEnabledFor(logging.DEBUG): LOG.debug("auth_config_hook(): %s", strutils.mask_password(six.text_type(config))) return config 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 osc-lib-1.9.0/osc_lib/cli/__init__.py0000666000175100017510000000000013227377263017373 0ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/cli/format_columns.py0000666000175100017510000000263713227377263020706 0ustar zuulzuul00000000000000# Copyright 2017 Huawei, Inc. All rights reserved. # # 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. # """Formattable column for specify content type""" from cliff import columns from osc_lib import utils class DictColumn(columns.FormattableColumn): """Format column for dict content""" def human_readable(self): return utils.format_dict(self._value) class DictListColumn(columns.FormattableColumn): """Format column for dict, key is string, value is list""" def human_readable(self): return utils.format_dict_of_list(self._value) class ListColumn(columns.FormattableColumn): """Format column for list content""" def human_readable(self): return utils.format_list(self._value) class ListDictColumn(columns.FormattableColumn): """Format column for list of dict content""" def human_readable(self): return utils.format_list_of_dicts(self._value) osc-lib-1.9.0/osc_lib/cli/parseractions.py0000666000175100017510000002362413227377263020532 0ustar zuulzuul00000000000000# Copyright 2013 OpenStack Foundation # # 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. # """argparse Custom Actions""" import argparse from osc_lib.i18n import _ class KeyValueAction(argparse.Action): """A custom action to parse arguments as key=value pairs Ensures that ``dest`` is a dict """ def __call__(self, parser, namespace, values, option_string=None): # Make sure we have an empty dict rather than None if getattr(namespace, self.dest, None) is None: setattr(namespace, self.dest, {}) # Add value if an assignment else remove it if '=' in values: values_list = values.split('=', 1) # NOTE(qtang): Prevent null key setting in property if '' == values_list[0]: msg = _("Property key must be specified: %s") raise argparse.ArgumentTypeError(msg % str(values)) else: getattr(namespace, self.dest, {}).update([values_list]) else: msg = _("Expected 'key=value' type, but got: %s") raise argparse.ArgumentTypeError(msg % str(values)) class MultiKeyValueAction(argparse.Action): """A custom action to parse arguments as key1=value1,key2=value2 pairs Ensure that ``dest`` is a list. The list will finally contain multiple dicts, with key=value pairs in them. NOTE: The arguments string should be a comma separated key-value pairs. And comma(',') and equal('=') may not be used in the key or value. """ def __init__(self, option_strings, dest, nargs=None, required_keys=None, optional_keys=None, **kwargs): """Initialize the action object, and parse customized options Required keys and optional keys can be specified when initializing the action to enable the key validation. If none of them specified, the key validation will be skipped. :param required_keys: a list of required keys :param optional_keys: a list of optional keys """ if nargs: msg = _("Parameter 'nargs' is not allowed, but got %s") raise ValueError(msg % nargs) super(MultiKeyValueAction, self).__init__(option_strings, dest, **kwargs) # required_keys: A list of keys that is required. None by default. if required_keys and not isinstance(required_keys, list): msg = _("'required_keys' must be a list") raise TypeError(msg) self.required_keys = set(required_keys or []) # optional_keys: A list of keys that is optional. None by default. if optional_keys and not isinstance(optional_keys, list): msg = _("'optional_keys' must be a list") raise TypeError(msg) self.optional_keys = set(optional_keys or []) def __call__(self, parser, namespace, values, metavar=None): # Make sure we have an empty list rather than None if getattr(namespace, self.dest, None) is None: setattr(namespace, self.dest, []) params = {} for kv in values.split(','): # Add value if an assignment else raise ArgumentTypeError if '=' in kv: kv_list = kv.split('=', 1) # NOTE(qtang): Prevent null key setting in property if '' == kv_list[0]: msg = _("Each property key must be specified: %s") raise argparse.ArgumentTypeError(msg % str(kv)) else: params.update([kv_list]) else: msg = _( "Expected comma separated 'key=value' pairs, but got: %s" ) raise argparse.ArgumentTypeError(msg % str(kv)) # Check key validation valid_keys = self.required_keys | self.optional_keys if valid_keys: invalid_keys = [k for k in params if k not in valid_keys] if invalid_keys: msg = _( "Invalid keys %(invalid_keys)s specified.\n" "Valid keys are: %(valid_keys)s" ) raise argparse.ArgumentTypeError(msg % { 'invalid_keys': ', '.join(invalid_keys), 'valid_keys': ', '.join(valid_keys), }) if self.required_keys: missing_keys = [k for k in self.required_keys if k not in params] if missing_keys: msg = _( "Missing required keys %(missing_keys)s.\n" "Required keys are: %(required_keys)s" ) raise argparse.ArgumentTypeError(msg % { 'missing_keys': ', '.join(missing_keys), 'required_keys': ', '.join(self.required_keys), }) # Update the dest dict getattr(namespace, self.dest, []).append(params) class MultiKeyValueCommaAction(MultiKeyValueAction): """Custom action to parse arguments from a set of key=value pair Ensures that ``dest`` is a dict. Parses dict by separating comma separated string into individual values Ex. key1=val1,val2,key2=val3 => {"key1": "val1,val2", "key2": "val3"} """ def __call__(self, parser, namespace, values, option_string=None): """Overwrite the __call__ function of MultiKeyValueAction This is done to handle scenarios where we may have comma seperated data as a single value. """ # Make sure we have an empty list rather than None if getattr(namespace, self.dest, None) is None: setattr(namespace, self.dest, []) params = {} key = '' for kv in values.split(','): # Add value if an assignment else raise ArgumentTypeError if '=' in kv: kv_list = kv.split('=', 1) # NOTE(qtang): Prevent null key setting in property if '' == kv_list[0]: msg = _("A key must be specified before '=': %s") raise argparse.ArgumentTypeError(msg % str(kv)) else: params.update([kv_list]) key = kv_list[0] else: # If the ',' split does not have key=value pair, then it # means the current value is a part of the previous # key=value pair, so append it. try: params[key] = "%s,%s" % (params[key], kv) except KeyError: msg = _("A key=value pair is required: %s") raise argparse.ArgumentTypeError(msg % str(kv)) # Check key validation valid_keys = self.required_keys | self.optional_keys if valid_keys: invalid_keys = [k for k in params if k not in valid_keys] if invalid_keys: msg = _( "Invalid keys %(invalid_keys)s specified.\n" "Valid keys are: %(valid_keys)s" ) raise argparse.ArgumentTypeError(msg % { 'invalid_keys': ', '.join(invalid_keys), 'valid_keys': ', '.join(valid_keys), }) if self.required_keys: missing_keys = [k for k in self.required_keys if k not in params] if missing_keys: msg = _( "Missing required keys %(missing_keys)s.\n" "Required keys are: %(required_keys)s" ) raise argparse.ArgumentTypeError(msg % { 'missing_keys': ', '.join(missing_keys), 'required_keys': ', '.join(self.required_keys), }) # Update the dest dict getattr(namespace, self.dest, []).append(params) class RangeAction(argparse.Action): """A custom action to parse a single value or a range of values Parses single integer values or a range of integer values delimited by a colon and returns a tuple of integers: '4' sets ``dest`` to (4, 4) '6:9' sets ``dest`` to (6, 9) """ def __call__(self, parser, namespace, values, option_string=None): range = values.split(':') if len(range) == 0: # Nothing passed, return a zero default setattr(namespace, self.dest, (0, 0)) elif len(range) == 1: # Only a single value is present setattr(namespace, self.dest, (int(range[0]), int(range[0]))) elif len(range) == 2: # Range of two values if int(range[0]) <= int(range[1]): setattr(namespace, self.dest, (int(range[0]), int(range[1]))) else: msg = _("Invalid range, %(min)s is not less than %(max)s") raise argparse.ArgumentError(self, msg % { 'min': range[0], 'max': range[1], }) else: # Too many values msg = _("Invalid range, too many values") raise argparse.ArgumentError(self, msg) class NonNegativeAction(argparse.Action): """A custom action to check whether the value is non-negative or not Ensures the value is >= 0. """ def __call__(self, parser, namespace, values, option_string=None): if int(values) >= 0: setattr(namespace, self.dest, values) else: msg = _("%s expected a non-negative integer") raise argparse.ArgumentTypeError(msg % str(option_string)) osc-lib-1.9.0/osc_lib/tests/0000775000175100017510000000000013227377613015664 5ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/tests/test_shell.py0000666000175100017510000004761213227377263020421 0ustar zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # # 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 copy import mock import os import sys import testtools from osc_lib import shell from osc_lib.tests import utils # NOTE(dtroyer): Attempt the import to detect if the SDK installed is new # enough to contain the os_client_config code. If so, use # that path for mocks. CONFIG_MOCK_BASE = "openstack.config.loader" try: from openstack.config import loader as config # noqa except ImportError: # Fall back to os-client-config CONFIG_MOCK_BASE = "os_client_config.config" DEFAULT_AUTH_URL = "http://127.0.0.1:5000/v2.0/" DEFAULT_PROJECT_ID = "xxxx-yyyy-zzzz" DEFAULT_PROJECT_NAME = "project" DEFAULT_DOMAIN_ID = "aaaa-bbbb-cccc" DEFAULT_DOMAIN_NAME = "default" DEFAULT_USER_DOMAIN_ID = "aaaa-bbbb-cccc" DEFAULT_USER_DOMAIN_NAME = "domain" DEFAULT_PROJECT_DOMAIN_ID = "aaaa-bbbb-cccc" DEFAULT_PROJECT_DOMAIN_NAME = "domain" DEFAULT_USERNAME = "username" DEFAULT_PASSWORD = "password" DEFAULT_CLOUD = "altocumulus" DEFAULT_REGION_NAME = "ZZ9_Plural_Z_Alpha" DEFAULT_TOKEN = "token" DEFAULT_SERVICE_URL = "http://127.0.0.1:8771/v3.0/" DEFAULT_AUTH_PLUGIN = "v2password" DEFAULT_INTERFACE = "internal" DEFAULT_COMPUTE_API_VERSION = "" DEFAULT_IDENTITY_API_VERSION = "" DEFAULT_IMAGE_API_VERSION = "" DEFAULT_VOLUME_API_VERSION = "" DEFAULT_NETWORK_API_VERSION = "" LIB_COMPUTE_API_VERSION = "" LIB_IDENTITY_API_VERSION = "" LIB_IMAGE_API_VERSION = "" LIB_VOLUME_API_VERSION = "" LIB_NETWORK_API_VERSION = "" CLOUD_1 = { 'clouds': { 'scc': { 'auth': { 'auth_url': DEFAULT_AUTH_URL, 'project_name': DEFAULT_PROJECT_NAME, 'username': 'zaphod', }, 'region_name': 'occ-cloud,krikkit', 'donut': 'glazed', 'interface': 'public', } } } CLOUD_2 = { 'clouds': { 'megacloud': { 'cloud': 'megadodo', 'auth': { 'project_name': 'heart-o-gold', 'username': 'zaphod', }, 'region_name': 'occ-cloud,krikkit,occ-env', 'log_file': '/tmp/test_log_file', 'log_level': 'debug', 'cert': 'mycert', 'key': 'mickey', } } } PUBLIC_1 = { 'public-clouds': { 'megadodo': { 'auth': { 'auth_url': DEFAULT_AUTH_URL, 'project_name': DEFAULT_PROJECT_NAME, }, 'region_name': 'occ-public', 'donut': 'cake', } } } # The option table values is a tuple of (, , ) # where is the test value to use, is True if this option # should be tested as a CLI option and is True of this option # should be tested as an environment variable. # Global options that should be parsed before shell.initialize_app() is called global_options = { '--os-cloud': (DEFAULT_CLOUD, True, True), '--os-region-name': (DEFAULT_REGION_NAME, True, True), '--os-default-domain': (DEFAULT_DOMAIN_NAME, True, True), '--os-cacert': ('/dev/null', True, True), '--timing': (True, True, False), '--os-interface': (DEFAULT_INTERFACE, True, True) } if shell.osprofiler_profiler: global_options['--os-profile'] = ('SECRET_KEY', True, True) class TestShellArgV(utils.TestShell): """Test the deferred help flag""" def setUp(self): super(TestShellArgV, self).setUp() def test_shell_argv(self): """Test argv decoding Python 2 does nothing with argv while Python 3 decodes it into Unicode before we ever see it. We manually decode when running under Python 2 so verify that we get the right argv types. Use the argv supplied by the test runner so we get actual Python runtime behaviour; we only need to check the type of argv[0] which will alwyas be present. """ with mock.patch( "osc_lib.shell.OpenStackShell.run", self.app, ): # Ensure type gets through unmolested through shell.main() argv = sys.argv shell.main(sys.argv) self.assertEqual(type(argv[0]), type(self.app.call_args[0][0][0])) # When shell.main() gets sys.argv itself it should be decoded shell.main() self.assertEqual(type(u'x'), type(self.app.call_args[0][0][0])) class TestShellHelp(utils.TestShell): """Test the deferred help flag""" def setUp(self): super(TestShellHelp, self).setUp() self.useFixture(utils.EnvFixture()) @testtools.skip("skip until bug 1444983 is resolved") def test_help_options(self): flag = "-h list server" kwargs = { "deferred_help": True, } with mock.patch(self.app_patch + ".initialize_app", self.app): _shell, _cmd = utils.make_shell(), flag utils.fake_execute(_shell, _cmd) self.assertEqual( kwargs["deferred_help"], _shell.options.deferred_help, ) class TestShellOptions(utils.TestShell): """Test the option handling by argparse and openstack.config.loader This covers getting the CLI options through the initial processing and validates the arguments to initialize_app() and occ_get_one() """ def setUp(self): super(TestShellOptions, self).setUp() self.useFixture(utils.EnvFixture()) def test_empty_auth(self): os.environ = {} self._assert_initialize_app_arg("", {}) self._assert_cloud_config_arg("", {}) def test_no_options(self): os.environ = {} self._assert_initialize_app_arg("", {}) self._assert_cloud_config_arg("", {}) def test_global_options(self): self._test_options_init_app(global_options) self._test_options_get_one_cloud(global_options) def test_global_env(self): self._test_env_init_app(global_options) self._test_env_get_one_cloud(global_options) class TestShellCli(utils.TestShell): """Test handling of specific global options _shell.options is the parsed command line from argparse _shell.client_manager.* are the values actually used """ def setUp(self): super(TestShellCli, self).setUp() env = {} self.useFixture(utils.EnvFixture(env.copy())) def test_shell_args_no_options(self): _shell = utils.make_shell() with mock.patch( "osc_lib.shell.OpenStackShell.initialize_app", self.app, ): utils.fake_execute(_shell, "list user") self.app.assert_called_with(["list", "user"]) def test_shell_args_tls_options(self): """Test the TLS verify and CA cert file options""" _shell = utils.make_shell() # Default utils.fake_execute(_shell, "module list") self.assertIsNone(_shell.options.verify) self.assertIsNone(_shell.options.insecure) self.assertIsNone(_shell.options.cacert) self.assertTrue(_shell.client_manager.verify) self.assertIsNone(_shell.client_manager.cacert) # --verify utils.fake_execute(_shell, "--verify module list") self.assertTrue(_shell.options.verify) self.assertIsNone(_shell.options.insecure) self.assertIsNone(_shell.options.cacert) self.assertTrue(_shell.client_manager.verify) self.assertIsNone(_shell.client_manager.cacert) # --insecure utils.fake_execute(_shell, "--insecure module list") self.assertIsNone(_shell.options.verify) self.assertTrue(_shell.options.insecure) self.assertIsNone(_shell.options.cacert) self.assertFalse(_shell.client_manager.verify) self.assertIsNone(_shell.client_manager.cacert) # --os-cacert utils.fake_execute(_shell, "--os-cacert foo module list") self.assertIsNone(_shell.options.verify) self.assertIsNone(_shell.options.insecure) self.assertEqual('foo', _shell.options.cacert) self.assertEqual('foo', _shell.client_manager.verify) self.assertEqual('foo', _shell.client_manager.cacert) # --os-cacert and --verify utils.fake_execute(_shell, "--os-cacert foo --verify module list") self.assertTrue(_shell.options.verify) self.assertIsNone(_shell.options.insecure) self.assertEqual('foo', _shell.options.cacert) self.assertEqual('foo', _shell.client_manager.verify) self.assertEqual('foo', _shell.client_manager.cacert) # --os-cacert and --insecure # NOTE(dtroyer): Per bug https://bugs.launchpad.net/bugs/1447784 # in this combination --insecure now overrides any # --os-cacert setting, where before --insecure # was ignored if --os-cacert was set. utils.fake_execute(_shell, "--os-cacert foo --insecure module list") self.assertIsNone(_shell.options.verify) self.assertTrue(_shell.options.insecure) self.assertEqual('foo', _shell.options.cacert) self.assertFalse(_shell.client_manager.verify) self.assertIsNone(_shell.client_manager.cacert) def test_shell_args_cert_options(self): """Test client cert options""" _shell = utils.make_shell() # Default utils.fake_execute(_shell, "module list") self.assertEqual('', _shell.options.cert) self.assertEqual('', _shell.options.key) self.assertIsNone(_shell.client_manager.cert) # --os-cert utils.fake_execute(_shell, "--os-cert mycert module list") self.assertEqual('mycert', _shell.options.cert) self.assertEqual('', _shell.options.key) self.assertEqual('mycert', _shell.client_manager.cert) # --os-key utils.fake_execute(_shell, "--os-key mickey module list") self.assertEqual('', _shell.options.cert) self.assertEqual('mickey', _shell.options.key) self.assertIsNone(_shell.client_manager.cert) # --os-cert and --os-key utils.fake_execute( _shell, "--os-cert mycert --os-key mickey module list" ) self.assertEqual('mycert', _shell.options.cert) self.assertEqual('mickey', _shell.options.key) self.assertEqual(('mycert', 'mickey'), _shell.client_manager.cert) @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_config_file") def test_shell_args_cloud_no_vendor(self, config_mock): """Test cloud config options without the vendor file""" config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_1)) _shell = utils.make_shell() utils.fake_execute( _shell, "--os-cloud scc module list", ) self.assertEqual( 'scc', _shell.cloud.name, ) # These come from clouds.yaml self.assertEqual( DEFAULT_AUTH_URL, _shell.cloud.config['auth']['auth_url'], ) self.assertEqual( DEFAULT_PROJECT_NAME, _shell.cloud.config['auth']['project_name'], ) self.assertEqual( 'zaphod', _shell.cloud.config['auth']['username'], ) self.assertEqual( 'occ-cloud', _shell.cloud.config['region_name'], ) self.assertEqual( 'occ-cloud', _shell.client_manager.region_name, ) self.assertEqual( 'glazed', _shell.cloud.config['donut'], ) self.assertEqual( 'public', _shell.cloud.config['interface'], ) self.assertIsNone(_shell.cloud.config['cert']) self.assertIsNone(_shell.cloud.config['key']) self.assertIsNone(_shell.client_manager.cert) @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_vendor_file") @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_config_file") def test_shell_args_cloud_public(self, config_mock, public_mock): """Test cloud config options with the vendor file""" config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) public_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) _shell = utils.make_shell() utils.fake_execute( _shell, "--os-cloud megacloud module list", ) self.assertEqual( 'megacloud', _shell.cloud.name, ) # These come from clouds-public.yaml self.assertEqual( DEFAULT_AUTH_URL, _shell.cloud.config['auth']['auth_url'], ) self.assertEqual( 'cake', _shell.cloud.config['donut'], ) # These come from clouds.yaml self.assertEqual( 'heart-o-gold', _shell.cloud.config['auth']['project_name'], ) self.assertEqual( 'zaphod', _shell.cloud.config['auth']['username'], ) self.assertEqual( 'occ-cloud', _shell.cloud.config['region_name'], ) self.assertEqual( 'occ-cloud', _shell.client_manager.region_name, ) self.assertEqual('mycert', _shell.cloud.config['cert']) self.assertEqual('mickey', _shell.cloud.config['key']) self.assertEqual(('mycert', 'mickey'), _shell.client_manager.cert) @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_vendor_file") @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_config_file") def test_shell_args_precedence(self, config_mock, vendor_mock): config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) _shell = utils.make_shell() # Test command option overriding config file value utils.fake_execute( _shell, "--os-cloud megacloud --os-region-name krikkit module list", ) self.assertEqual( 'megacloud', _shell.cloud.name, ) # These come from clouds-public.yaml self.assertEqual( DEFAULT_AUTH_URL, _shell.cloud.config['auth']['auth_url'], ) self.assertEqual( 'cake', _shell.cloud.config['donut'], ) # These come from clouds.yaml self.assertEqual( 'heart-o-gold', _shell.cloud.config['auth']['project_name'], ) self.assertEqual( 'zaphod', _shell.cloud.config['auth']['username'], ) self.assertEqual( 'krikkit', _shell.cloud.config['region_name'], ) self.assertEqual( 'krikkit', _shell.client_manager.region_name, ) class TestShellCliPrecedence(utils.TestShell): """Test option precedencr order""" def setUp(self): super(TestShellCliPrecedence, self).setUp() env = { 'OS_CLOUD': 'megacloud', 'OS_REGION_NAME': 'occ-env', } self.useFixture(utils.EnvFixture(env.copy())) @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_vendor_file") @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_config_file") def test_shell_args_precedence_1(self, config_mock, vendor_mock): """Test environment overriding occ""" config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) _shell = utils.make_shell() # Test env var utils.fake_execute( _shell, "module list", ) self.assertEqual( 'megacloud', _shell.cloud.name, ) # These come from clouds-public.yaml self.assertEqual( DEFAULT_AUTH_URL, _shell.cloud.config['auth']['auth_url'], ) self.assertEqual( 'cake', _shell.cloud.config['donut'], ) # These come from clouds.yaml self.assertEqual( 'heart-o-gold', _shell.cloud.config['auth']['project_name'], ) self.assertEqual( 'zaphod', _shell.cloud.config['auth']['username'], ) # These come from the environment self.assertEqual( 'occ-env', _shell.cloud.config['region_name'], ) self.assertEqual( 'occ-env', _shell.client_manager.region_name, ) @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_vendor_file") @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_config_file") def test_shell_args_precedence_2(self, config_mock, vendor_mock): """Test command line overriding environment and occ""" config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) _shell = utils.make_shell() # Test command option overriding config file value utils.fake_execute( _shell, "--os-region-name krikkit list user", ) self.assertEqual( 'megacloud', _shell.cloud.name, ) # These come from clouds-public.yaml self.assertEqual( DEFAULT_AUTH_URL, _shell.cloud.config['auth']['auth_url'], ) self.assertEqual( 'cake', _shell.cloud.config['donut'], ) # These come from clouds.yaml self.assertEqual( 'heart-o-gold', _shell.cloud.config['auth']['project_name'], ) self.assertEqual( 'zaphod', _shell.cloud.config['auth']['username'], ) # These come from the command line self.assertEqual( 'krikkit', _shell.cloud.config['region_name'], ) self.assertEqual( 'krikkit', _shell.client_manager.region_name, ) @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_vendor_file") @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_config_file") def test_shell_args_precedence_3(self, config_mock, vendor_mock): """Test command line overriding environment and occ""" config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_1)) vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) _shell = utils.make_shell() # Test command option overriding config file value utils.fake_execute( _shell, "--os-cloud scc --os-region-name krikkit list user", ) self.assertEqual( 'scc', _shell.cloud.name, ) # These come from clouds-public.yaml self.assertEqual( DEFAULT_AUTH_URL, _shell.cloud.config['auth']['auth_url'], ) self.assertEqual( 'glazed', _shell.cloud.config['donut'], ) # These come from clouds.yaml self.assertEqual( DEFAULT_PROJECT_NAME, _shell.cloud.config['auth']['project_name'], ) self.assertEqual( 'zaphod', _shell.cloud.config['auth']['username'], ) # These come from the command line self.assertEqual( 'krikkit', _shell.cloud.config['region_name'], ) self.assertEqual( 'krikkit', _shell.client_manager.region_name, ) osc-lib-1.9.0/osc_lib/tests/cli/0000775000175100017510000000000013227377613016433 5ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/tests/cli/test_parseractions.py0000666000175100017510000003055513227377263022734 0ustar zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # # 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 argparse from osc_lib.cli import parseractions from osc_lib.tests import utils class TestKeyValueAction(utils.TestCase): def setUp(self): super(TestKeyValueAction, self).setUp() self.parser = argparse.ArgumentParser() # Set up our typical usage self.parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, default={'green': '20%', 'format': '#rgb'}, help='Property to store for this volume ' '(repeat option to set multiple properties)', ) def test_good_values(self): results = self.parser.parse_args([ '--property', 'red=', '--property', 'green=100%', '--property', 'blue=50%', ]) actual = getattr(results, 'property', {}) # All should pass through unmolested expect = {'red': '', 'green': '100%', 'blue': '50%', 'format': '#rgb'} self.assertEqual(expect, actual) def test_error_values(self): data_list = [ ['--property', 'red', ], ['--property', '=', ], ['--property', '=red', ] ] for data in data_list: self.assertRaises(argparse.ArgumentTypeError, self.parser.parse_args, data) class TestMultiKeyValueAction(utils.TestCase): def setUp(self): super(TestMultiKeyValueAction, self).setUp() self.parser = argparse.ArgumentParser() # Set up our typical usage self.parser.add_argument( '--test', metavar='req1=xxx,req2=yyy', action=parseractions.MultiKeyValueAction, dest='test', default=None, required_keys=['req1', 'req2'], optional_keys=['opt1', 'opt2'], help='Test' ) def test_good_values(self): results = self.parser.parse_args([ '--test', 'req1=aaa,req2=bbb', '--test', 'req1=,req2=', ]) actual = getattr(results, 'test', []) expect = [ {'req1': 'aaa', 'req2': 'bbb'}, {'req1': '', 'req2': ''}, ] self.assertItemsEqual(expect, actual) def test_empty_required_optional(self): self.parser.add_argument( '--test-empty', metavar='req1=xxx,req2=yyy', action=parseractions.MultiKeyValueAction, dest='test_empty', default=None, required_keys=[], optional_keys=[], help='Test' ) results = self.parser.parse_args([ '--test-empty', 'req1=aaa,req2=bbb', '--test-empty', 'req1=,req2=', ]) actual = getattr(results, 'test_empty', []) expect = [ {'req1': 'aaa', 'req2': 'bbb'}, {'req1': '', 'req2': ''}, ] self.assertItemsEqual(expect, actual) def test_error_values_with_comma(self): data_list = [ ['--test', 'mmm,nnn=zzz', ], ['--test', 'nnn=zzz,=', ], ['--test', 'nnn=zzz,=zzz', ] ] for data in data_list: self.assertRaises(argparse.ArgumentTypeError, self.parser.parse_args, data) def test_error_values_without_comma(self): self.assertRaises( argparse.ArgumentTypeError, self.parser.parse_args, [ '--test', 'mmmnnn', ] ) def test_missing_key(self): self.assertRaises( argparse.ArgumentTypeError, self.parser.parse_args, [ '--test', 'req2=ddd', ] ) def test_invalid_key(self): self.assertRaises( argparse.ArgumentTypeError, self.parser.parse_args, [ '--test', 'req1=aaa,req2=bbb,aaa=req1', ] ) def test_required_keys_not_list(self): self.assertRaises( TypeError, self.parser.add_argument, '--test-required-dict', metavar='req1=xxx,req2=yyy', action=parseractions.MultiKeyValueAction, dest='test_required_dict', default=None, required_keys={'aaa': 'bbb'}, optional_keys=['opt1', 'opt2'], help='Test' ) def test_optional_keys_not_list(self): self.assertRaises( TypeError, self.parser.add_argument, '--test-optional-dict', metavar='req1=xxx,req2=yyy', action=parseractions.MultiKeyValueAction, dest='test_optional_dict', default=None, required_keys=['req1', 'req2'], optional_keys={'aaa': 'bbb'}, help='Test' ) class TestMultiKeyValueCommaAction(utils.TestCase): def setUp(self): super(TestMultiKeyValueCommaAction, self).setUp() self.parser = argparse.ArgumentParser() # Typical usage self.parser.add_argument( '--test', metavar='req1=xxx,yyy', action=parseractions.MultiKeyValueCommaAction, dest='test', default=None, required_keys=['req1'], optional_keys=['opt2'], help='Test', ) def test_mkvca_required(self): results = self.parser.parse_args([ '--test', 'req1=aaa,bbb', ]) actual = getattr(results, 'test', []) expect = [ {'req1': 'aaa,bbb'}, ] self.assertItemsEqual(expect, actual) results = self.parser.parse_args([ '--test', 'req1=', ]) actual = getattr(results, 'test', []) expect = [ {'req1': ''}, ] self.assertItemsEqual(expect, actual) results = self.parser.parse_args([ '--test', 'req1=aaa,bbb', '--test', 'req1=', ]) actual = getattr(results, 'test', []) expect = [ {'req1': 'aaa,bbb'}, {'req1': ''}, ] self.assertItemsEqual(expect, actual) def test_mkvca_optional(self): results = self.parser.parse_args([ '--test', 'req1=aaa,bbb', ]) actual = getattr(results, 'test', []) expect = [ {'req1': 'aaa,bbb'}, ] self.assertItemsEqual(expect, actual) results = self.parser.parse_args([ '--test', 'req1=aaa,bbb', '--test', 'req1=,opt2=ccc', ]) actual = getattr(results, 'test', []) expect = [ {'req1': 'aaa,bbb'}, {'req1': '', 'opt2': 'ccc'}, ] self.assertItemsEqual(expect, actual) try: results = self.parser.parse_args([ '--test', 'req1=aaa,bbb', '--test', 'opt2=ccc', ]) self.fail('ArgumentTypeError should be raised') except argparse.ArgumentTypeError as e: self.assertEqual( 'Missing required keys req1.\nRequired keys are: req1', str(e), ) def test_mkvca_multiples(self): results = self.parser.parse_args([ '--test', 'req1=aaa,bbb,opt2=ccc', ]) actual = getattr(results, 'test', []) expect = [{ 'req1': 'aaa,bbb', 'opt2': 'ccc', }] self.assertItemsEqual(expect, actual) def test_mkvca_no_required_optional(self): self.parser.add_argument( '--test-empty', metavar='req1=xxx,yyy', action=parseractions.MultiKeyValueCommaAction, dest='test_empty', default=None, required_keys=[], optional_keys=[], help='Test', ) results = self.parser.parse_args([ '--test-empty', 'req1=aaa,bbb', ]) actual = getattr(results, 'test_empty', []) expect = [ {'req1': 'aaa,bbb'}, ] self.assertItemsEqual(expect, actual) results = self.parser.parse_args([ '--test-empty', 'xyz=aaa,bbb', ]) actual = getattr(results, 'test_empty', []) expect = [ {'xyz': 'aaa,bbb'}, ] self.assertItemsEqual(expect, actual) def test_mkvca_invalid_key(self): try: self.parser.parse_args([ '--test', 'req1=aaa,bbb=', ]) self.fail('ArgumentTypeError should be raised') except argparse.ArgumentTypeError as e: self.assertIn( 'Invalid keys bbb specified.\nValid keys are:', str(e), ) try: self.parser.parse_args([ '--test', 'nnn=aaa', ]) self.fail('ArgumentTypeError should be raised') except argparse.ArgumentTypeError as e: self.assertIn( 'Invalid keys nnn specified.\nValid keys are:', str(e), ) def test_mkvca_value_no_key(self): try: self.parser.parse_args([ '--test', 'req1=aaa,=bbb', ]) self.fail('ArgumentTypeError should be raised') except argparse.ArgumentTypeError as e: self.assertEqual( "A key must be specified before '=': =bbb", str(e), ) try: self.parser.parse_args([ '--test', '=nnn', ]) self.fail('ArgumentTypeError should be raised') except argparse.ArgumentTypeError as e: self.assertEqual( "A key must be specified before '=': =nnn", str(e), ) try: self.parser.parse_args([ '--test', 'nnn', ]) self.fail('ArgumentTypeError should be raised') except argparse.ArgumentTypeError as e: self.assertIn( 'A key=value pair is required:', str(e), ) def test_mkvca_required_keys_not_list(self): self.assertRaises( TypeError, self.parser.add_argument, '--test-required-dict', metavar='req1=xxx', action=parseractions.MultiKeyValueCommaAction, dest='test_required_dict', default=None, required_keys={'aaa': 'bbb'}, optional_keys=['opt1', 'opt2'], help='Test', ) def test_mkvca_optional_keys_not_list(self): self.assertRaises( TypeError, self.parser.add_argument, '--test-optional-dict', metavar='req1=xxx', action=parseractions.MultiKeyValueCommaAction, dest='test_optional_dict', default=None, required_keys=['req1', 'req2'], optional_keys={'aaa': 'bbb'}, help='Test', ) class TestNonNegativeAction(utils.TestCase): def setUp(self): super(TestNonNegativeAction, self).setUp() self.parser = argparse.ArgumentParser() # Set up our typical usage self.parser.add_argument( '--foo', metavar='', type=int, action=parseractions.NonNegativeAction, ) def test_negative_values(self): self.assertRaises( argparse.ArgumentTypeError, self.parser.parse_args, "--foo -1".split() ) def test_zero_values(self): results = self.parser.parse_args( '--foo 0'.split() ) actual = getattr(results, 'foo', None) self.assertEqual(actual, 0) def test_positive_values(self): results = self.parser.parse_args( '--foo 1'.split() ) actual = getattr(results, 'foo', None) self.assertEqual(actual, 1) osc-lib-1.9.0/osc_lib/tests/cli/test_client_config.py0000666000175100017510000002027013227377263022653 0ustar zuulzuul00000000000000# 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. # from osc_lib.cli import client_config from osc_lib.tests import utils class TestOSCConfig(utils.TestCase): def setUp(self): super(TestOSCConfig, self).setUp() self.cloud = client_config.OSC_Config() def test_auth_select_default_plugin(self): config = { 'auth_type': 'admin_token', } ret_config = self.cloud._auth_select_default_plugin(config) self.assertEqual('admin_token', ret_config['auth_type']) def test_auth_select_default_plugin_password(self): config = { 'username': 'fred', } ret_config = self.cloud._auth_select_default_plugin(config) self.assertEqual('password', ret_config['auth_type']) self.assertEqual('fred', ret_config['username']) def test_auth_select_default_plugin_password_v2(self): config = { 'identity_api_version': '2', 'username': 'fred', } ret_config = self.cloud._auth_select_default_plugin(config) self.assertEqual('v2password', ret_config['auth_type']) self.assertEqual('fred', ret_config['username']) def test_auth_select_default_plugin_password_v2_int(self): config = { 'identity_api_version': 2, 'username': 'fred', } ret_config = self.cloud._auth_select_default_plugin(config) self.assertEqual('v2password', ret_config['auth_type']) self.assertEqual('fred', ret_config['username']) def test_auth_select_default_plugin_password_v3(self): config = { 'identity_api_version': '3', 'username': 'fred', } ret_config = self.cloud._auth_select_default_plugin(config) self.assertEqual('v3password', ret_config['auth_type']) self.assertEqual('fred', ret_config['username']) def test_auth_select_default_plugin_password_v3_int(self): config = { 'identity_api_version': 3, 'username': 'fred', } ret_config = self.cloud._auth_select_default_plugin(config) self.assertEqual('v3password', ret_config['auth_type']) self.assertEqual('fred', ret_config['username']) def test_auth_select_default_plugin_token(self): config = { 'token': 'subway', } ret_config = self.cloud._auth_select_default_plugin(config) self.assertEqual('token', ret_config['auth_type']) self.assertEqual('subway', ret_config['token']) def test_auth_select_default_plugin_token_v2(self): config = { 'identity_api_version': '2.2', 'token': 'subway', } ret_config = self.cloud._auth_select_default_plugin(config) self.assertEqual('v2token', ret_config['auth_type']) self.assertEqual('subway', ret_config['token']) def test_auth_select_default_plugin_token_v3(self): config = { 'identity_api_version': '3', 'token': 'subway', } ret_config = self.cloud._auth_select_default_plugin(config) self.assertEqual('v3token', ret_config['auth_type']) self.assertEqual('subway', ret_config['token']) def test_auth_v2_arguments(self): config = { 'identity_api_version': '2', 'auth_type': 'v2password', 'auth': { 'username': 'fred', }, } ret_config = self.cloud._auth_v2_arguments(config) self.assertEqual('fred', ret_config['auth']['username']) self.assertFalse('tenant_id' in ret_config['auth']) self.assertFalse('tenant_name' in ret_config['auth']) config = { 'identity_api_version': '3', 'auth_type': 'v3password', 'auth': { 'username': 'fred', 'project_id': 'id', }, } ret_config = self.cloud._auth_v2_arguments(config) self.assertEqual('fred', ret_config['auth']['username']) self.assertFalse('tenant_id' in ret_config['auth']) self.assertFalse('tenant_name' in ret_config['auth']) config = { 'identity_api_version': '2', 'auth_type': 'v2password', 'auth': { 'username': 'fred', 'project_id': 'id', }, } ret_config = self.cloud._auth_v2_arguments(config) self.assertEqual('id', ret_config['auth']['tenant_id']) self.assertFalse('tenant_name' in ret_config['auth']) config = { 'identity_api_version': '2', 'auth_type': 'v2password', 'auth': { 'username': 'fred', 'project_name': 'name', }, } ret_config = self.cloud._auth_v2_arguments(config) self.assertFalse('tenant_id' in ret_config['auth']) self.assertEqual('name', ret_config['auth']['tenant_name']) def test_auth_v2_ignore_v3(self): config = { 'identity_api_version': '2', 'auth_type': 'v2password', 'auth': { 'username': 'fred', 'project_id': 'id', 'project_domain_id': 'bad', }, } ret_config = self.cloud._auth_v2_ignore_v3(config) self.assertEqual('fred', ret_config['auth']['username']) self.assertFalse('project_domain_id' in ret_config['auth']) def test_auth_default_domain_not_set(self): config = { 'identity_api_version': '3', 'auth_type': 'v3oidcpassword', 'default_domain': 'default', 'auth': { 'username': 'fred', 'project_id': 'id', }, } ret_config = self.cloud._auth_default_domain(config) self.assertEqual('v3oidcpassword', ret_config['auth_type']) self.assertEqual('default', ret_config['default_domain']) self.assertEqual('fred', ret_config['auth']['username']) self.assertNotIn('project_domain_id', ret_config['auth']) self.assertNotIn('user_domain_id', ret_config['auth']) def test_auth_default_domain_use_default(self): config = { 'identity_api_version': '3', 'auth_type': 'v3password', 'default_domain': 'default', 'auth': { 'username': 'fred', 'project_id': 'id', }, } ret_config = self.cloud._auth_default_domain(config) self.assertEqual('v3password', ret_config['auth_type']) self.assertEqual('default', ret_config['default_domain']) self.assertEqual('fred', ret_config['auth']['username']) self.assertEqual('default', ret_config['auth']['project_domain_id']) self.assertEqual('default', ret_config['auth']['user_domain_id']) def test_auth_default_domain_use_given(self): config = { 'identity_api_version': '3', 'auth_type': 'v3password', 'default_domain': 'default', 'auth': { 'username': 'fred', 'project_id': 'id', 'project_domain_id': 'proj', 'user_domain_id': 'use' }, } ret_config = self.cloud._auth_default_domain(config) self.assertEqual('v3password', ret_config['auth_type']) self.assertEqual('default', ret_config['default_domain']) self.assertEqual('fred', ret_config['auth']['username']) self.assertEqual('proj', ret_config['auth']['project_domain_id']) self.assertEqual('use', ret_config['auth']['user_domain_id']) def test_auth_config_hook_default(self): config = {} ret_config = self.cloud.auth_config_hook(config) self.assertEqual('password', ret_config['auth_type']) osc-lib-1.9.0/osc_lib/tests/cli/__init__.py0000666000175100017510000000000013227377263020535 0ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/tests/cli/test_format_columns.py0000666000175100017510000000437313227377263023106 0ustar zuulzuul00000000000000# Copyright 2017 Huawei, Inc. All rights reserved. # # 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. # from osc_lib.cli import format_columns from osc_lib.tests import utils class TestDictColumn(utils.TestCase): def test_dict_column(self): dict_content = { 'key1': 'value1', 'key2': 'value2', } col = format_columns.DictColumn(dict_content) self.assertEqual(dict_content, col.machine_readable()) self.assertEqual("key1='value1', key2='value2'", col.human_readable()) class TestDictListColumn(utils.TestCase): def test_dict_list_column(self): dict_list_content = {'public': ['2001:db8::8', '172.24.4.6'], 'private': ['2000:db7::7', '192.24.4.6']} col = format_columns.DictListColumn(dict_list_content) self.assertEqual(dict_list_content, col.machine_readable()) self.assertEqual('private=192.24.4.6, 2000:db7::7; ' 'public=172.24.4.6, 2001:db8::8', col.human_readable()) class TestListColumn(utils.TestCase): def test_list_column(self): list_content = [ 'key1', 'key2', ] col = format_columns.ListColumn(list_content) self.assertEqual(list_content, col.machine_readable()) self.assertEqual("key1, key2", col.human_readable()) class TestListDictColumn(utils.TestCase): def test_list_dict_column(self): list_dict_content = [ {'key1': 'value1'}, {'key2': 'value2'}, ] col = format_columns.ListDictColumn(list_dict_content) self.assertEqual(list_dict_content, col.machine_readable()) self.assertEqual("key1='value1'\nkey2='value2'", col.human_readable()) osc-lib-1.9.0/osc_lib/tests/fakes.py0000666000175100017510000001260113227377263017332 0ustar zuulzuul00000000000000# Copyright 2013 Nebula Inc. # # 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 import six import sys from keystoneauth1 import fixture AUTH_TOKEN = "foobar" AUTH_URL = "http://0.0.0.0" USERNAME = "itchy" PASSWORD = "scratchy" PROJECT_NAME = "poochie" PROJECT_ID = "30c3da29-61f5-4b7b-8eb2-3d18287428c7" REGION_NAME = "richie" INTERFACE = "catchy" VERSION = "3" SERVICE_PROVIDER_ID = "bob" TEST_RESPONSE_DICT = fixture.V2Token(token_id=AUTH_TOKEN, user_name=USERNAME) _s = TEST_RESPONSE_DICT.add_service('identity', name='keystone') _s.add_endpoint(AUTH_URL + ':5000/v2.0') _s = TEST_RESPONSE_DICT.add_service('network', name='neutron') _s.add_endpoint(AUTH_URL + ':9696') _s = TEST_RESPONSE_DICT.add_service('compute', name='nova') _s.add_endpoint(AUTH_URL + ':8774/v2') _s = TEST_RESPONSE_DICT.add_service('image', name='glance') _s.add_endpoint(AUTH_URL + ':9292') _s = TEST_RESPONSE_DICT.add_service('object', name='swift') _s.add_endpoint(AUTH_URL + ':8080/v1') TEST_RESPONSE_DICT_V3 = fixture.V3Token(user_name=USERNAME) TEST_RESPONSE_DICT_V3.set_project_scope() TEST_VERSIONS = fixture.DiscoveryList(href=AUTH_URL) def to_unicode_dict(catalog_dict): """Converts dict to unicode dict""" if isinstance(catalog_dict, dict): return {to_unicode_dict(key): to_unicode_dict(value) for key, value in catalog_dict.items()} elif isinstance(catalog_dict, list): return [to_unicode_dict(element) for element in catalog_dict] elif isinstance(catalog_dict, str): return catalog_dict + u"" else: return catalog_dict class FakeStdout(object): def __init__(self): self.content = [] def write(self, text): self.content.append(text) def make_string(self): result = '' for line in self.content: result = result + line return result class FakeLog(object): def __init__(self): self.messages = {} def debug(self, msg): self.messages['debug'] = msg def info(self, msg): self.messages['info'] = msg def warning(self, msg): self.messages['warning'] = msg def error(self, msg): self.messages['error'] = msg def critical(self, msg): self.messages['critical'] = msg class FakeApp(object): def __init__(self, _stdout, _log): self.stdout = _stdout self.client_manager = None self.stdin = sys.stdin self.stdout = _stdout or sys.stdout self.stderr = sys.stderr self.log = _log class FakeOptions(object): def __init__(self, **kwargs): self.os_beta_command = False class FakeClientManager(object): def __init__(self): self.compute = None self.identity = None self.image = None self.object_store = None self.volume = None self.network = None self.session = None self.auth_ref = None self.auth_plugin_name = None def get_configuration(self): return { 'auth': { 'username': USERNAME, 'password': PASSWORD, 'token': AUTH_TOKEN, }, 'region': REGION_NAME, 'identity_api_version': VERSION, } class FakeResource(object): def __init__(self, manager=None, info=None, loaded=False, methods=None): """Set attributes and methods for a resource. :param manager: The resource manager :param Dictionary info: A dictionary with all attributes :param bool loaded: True if the resource is loaded in memory :param Dictionary methods: A dictionary with all methods """ info = info or {} methods = methods or {} self.__name__ = type(self).__name__ self.manager = manager self._info = info self._add_details(info) self._add_methods(methods) self._loaded = loaded def _add_details(self, info): for (k, v) in six.iteritems(info): setattr(self, k, v) def _add_methods(self, methods): """Fake methods with MagicMock objects. For each <@key, @value> pairs in methods, add an callable MagicMock object named @key as an attribute, and set the mock's return_value to @value. When users access the attribute with (), @value will be returned, which looks like a function call. """ for (name, ret) in six.iteritems(methods): method = mock.MagicMock(return_value=ret) setattr(self, name, method) def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) def keys(self): return self._info.keys() osc-lib-1.9.0/osc_lib/tests/__init__.py0000666000175100017510000000000013227377263017766 0ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/tests/command/0000775000175100017510000000000013227377613017302 5ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/tests/command/test_commandmanager.py0000666000175100017510000000656713227377263023705 0ustar zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # # 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 osc_lib.command import commandmanager from osc_lib.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, namespace): if namespace == 'test': self.commands['one'] = FAKE_CMD_ONE self.commands['two'] = FAKE_CMD_TWO self.group_list.append(namespace) elif namespace == 'greek': self.commands['alpha'] = FAKE_CMD_ALPHA self.commands['beta'] = FAKE_CMD_BETA self.group_list.append(namespace) 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(mock_cmd_one, cmd_mock) # Find a command added in initialization cmd_one, name, args = mgr.find_command(['one']) self.assertEqual(FAKE_CMD_ONE, cmd_one) # Load another command group mgr.add_command_group('greek') # Find a new command cmd_alpha, name, args = mgr.find_command(['alpha']) self.assertEqual(FAKE_CMD_ALPHA, cmd_alpha) # Ensure that the original commands were not overwritten cmd_two, name, args = mgr.find_command(['two']) self.assertEqual(FAKE_CMD_TWO, cmd_two) def test_get_command_groups(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(mock_cmd_one, cmd_mock) # Load another command group mgr.add_command_group('greek') gl = mgr.get_command_groups() self.assertEqual(['test', 'greek'], gl) def test_get_command_names(self): mock_cmd_one = mock.Mock() mock_cmd_one.name = 'one' mock_cmd_two = mock.Mock() mock_cmd_two.name = 'cmd two' mock_pkg_resources = mock.Mock( return_value=[mock_cmd_one, mock_cmd_two], ) with mock.patch( 'pkg_resources.iter_entry_points', mock_pkg_resources, ) as iter_entry_points: mgr = commandmanager.CommandManager('test') iter_entry_points.assert_called_once_with('test') cmds = mgr.get_command_names('test') self.assertEqual(['one', 'cmd two'], cmds) osc-lib-1.9.0/osc_lib/tests/command/test_command.py0000666000175100017510000000320613227377263022335 0ustar zuulzuul00000000000000# Copyright 2016 NEC Corporation # # 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 osc_lib.command import command from osc_lib import exceptions from osc_lib.tests import fakes as test_fakes from osc_lib.tests import utils as test_utils class FakeCommand(command.Command): def take_action(self, parsed_args): pass class TestCommand(test_utils.TestCase): def test_command_has_logger(self): cmd = FakeCommand(mock.Mock(), mock.Mock()) self.assertTrue(hasattr(cmd, 'log')) self.assertEqual( 'osc_lib.tests.command.test_command.FakeCommand', cmd.log.name, ) def test_validate_os_beta_command_enabled(self): cmd = FakeCommand(mock.Mock(), mock.Mock()) cmd.app = mock.Mock() cmd.app.options = test_fakes.FakeOptions() # No exception is raised when enabled. cmd.app.options.os_beta_command = True cmd.validate_os_beta_command_enabled() cmd.app.options.os_beta_command = False self.assertRaises( exceptions.CommandError, cmd.validate_os_beta_command_enabled, ) osc-lib-1.9.0/osc_lib/tests/command/__init__.py0000666000175100017510000000000013227377263021404 0ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/tests/command/test_timing.py0000666000175100017510000000540413227377263022210 0ustar zuulzuul00000000000000# 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. # """Test Timing pseudo-command""" import datetime from osc_lib.command import timing from osc_lib.tests import fakes from osc_lib.tests import utils timing_url = 'GET http://localhost:5000' timing_elapsed = 0.872809 class FakeGenericClient(object): def __init__(self, **kwargs): self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] class TestTiming(utils.TestCommand): columns = ( 'URL', 'Seconds', ) def setUp(self): super(TestTiming, self).setUp() self.app.timing_data = [] self.app.client_manager.compute = FakeGenericClient( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) self.app.client_manager.volume = FakeGenericClient( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) # Get the command object to test self.cmd = timing.Timing(self.app, None) def test_timing_list_no_data(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) datalist = [ ('Total', 0.0,) ] self.assertEqual(datalist, data) def test_timing_list(self): self.app.timing_data = [( timing_url, datetime.timedelta(microseconds=timing_elapsed * 1000000), )] arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) datalist = [ (timing_url, timing_elapsed), ('Total', timing_elapsed), ] self.assertEqual(datalist, data) osc-lib-1.9.0/osc_lib/tests/api/0000775000175100017510000000000013227377613016435 5ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/tests/api/fakes.py0000666000175100017510000000252013227377263020102 0ustar zuulzuul00000000000000# 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. # """API Test Fakes""" from keystoneauth1 import session from requests_mock.contrib import fixture from osc_lib.tests import utils RESP_ITEM_1 = { 'id': '1', 'name': 'alpha', 'status': 'UP', 'props': {'a': 1, 'b': 2}, } RESP_ITEM_2 = { 'id': '2', 'name': 'beta', 'status': 'DOWN', 'props': {'a': 2, 'b': 2}, } RESP_ITEM_3 = { 'id': '3', 'name': 'delta', 'status': 'UP', 'props': {'a': 3, 'b': 1}, } LIST_RESP = [RESP_ITEM_1, RESP_ITEM_2] LIST_BODY = { 'p1': 'xxx', 'p2': 'yyy', } class TestSession(utils.TestCase): BASE_URL = 'https://api.example.com:1234/test' def setUp(self): super(TestSession, self).setUp() self.sess = session.Session() self.requests_mock = self.useFixture(fixture.Fixture()) osc-lib-1.9.0/osc_lib/tests/api/test_utils.py0000666000175100017510000000646713227377263021226 0ustar zuulzuul00000000000000# 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. # """API Utilities Library Tests""" import copy from osc_lib.api import api from osc_lib.api import utils as api_utils from osc_lib.tests.api import fakes as api_fakes class TestBaseAPIFilter(api_fakes.TestSession): """The filters can be tested independently""" def setUp(self): super(TestBaseAPIFilter, self).setUp() self.api = api.BaseAPI( session=self.sess, endpoint=self.BASE_URL, ) self.input_list = [ api_fakes.RESP_ITEM_1, api_fakes.RESP_ITEM_2, api_fakes.RESP_ITEM_3, ] def test_simple_filter_none(self): output = api_utils.simple_filter( ) self.assertIsNone(output) def test_simple_filter_no_attr(self): output = api_utils.simple_filter( copy.deepcopy(self.input_list), ) self.assertEqual(self.input_list, output) def test_simple_filter_attr_only(self): output = api_utils.simple_filter( copy.deepcopy(self.input_list), attr='status', ) self.assertEqual(self.input_list, output) def test_simple_filter_attr_value(self): output = api_utils.simple_filter( copy.deepcopy(self.input_list), attr='status', value='', ) self.assertEqual([], output) output = api_utils.simple_filter( copy.deepcopy(self.input_list), attr='status', value='UP', ) self.assertEqual( [api_fakes.RESP_ITEM_1, api_fakes.RESP_ITEM_3], output, ) output = api_utils.simple_filter( copy.deepcopy(self.input_list), attr='fred', value='UP', ) self.assertEqual([], output) def test_simple_filter_prop_attr_only(self): output = api_utils.simple_filter( copy.deepcopy(self.input_list), attr='b', property_field='props', ) self.assertEqual(self.input_list, output) output = api_utils.simple_filter( copy.deepcopy(self.input_list), attr='status', property_field='props', ) self.assertEqual(self.input_list, output) def test_simple_filter_prop_attr_value(self): output = api_utils.simple_filter( copy.deepcopy(self.input_list), attr='b', value=2, property_field='props', ) self.assertEqual( [api_fakes.RESP_ITEM_1, api_fakes.RESP_ITEM_2], output, ) output = api_utils.simple_filter( copy.deepcopy(self.input_list), attr='b', value=9, property_field='props', ) self.assertEqual([], output) osc-lib-1.9.0/osc_lib/tests/api/__init__.py0000666000175100017510000000000013227377263020537 0ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/tests/api/test_api.py0000666000175100017510000004054313227377263020630 0ustar zuulzuul00000000000000# 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. # """Base API Library Tests""" from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1 import session from osc_lib.api import api from osc_lib import exceptions from osc_lib.tests.api import fakes as api_fakes class TestBaseAPIDefault(api_fakes.TestSession): def setUp(self): super(TestBaseAPIDefault, self).setUp() self.api = api.BaseAPI() def test_baseapi_request_no_url(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.RESP_ITEM_1, status_code=200, ) self.assertRaises( ksa_exceptions.EndpointNotFound, self.api._request, 'GET', '', ) self.assertIsNotNone(self.api.session) self.assertNotEqual(self.sess, self.api.session) def test_baseapi_request_url(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.RESP_ITEM_1, status_code=200, ) ret = self.api._request('GET', self.BASE_URL + '/qaz') self.assertEqual(api_fakes.RESP_ITEM_1, ret.json()) self.assertIsNotNone(self.api.session) self.assertNotEqual(self.sess, self.api.session) def test_baseapi_request_url_path(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.RESP_ITEM_1, status_code=200, ) self.assertRaises( ksa_exceptions.EndpointNotFound, self.api._request, 'GET', '/qaz', ) self.assertIsNotNone(self.api.session) self.assertNotEqual(self.sess, self.api.session) def test_baseapi_request_session(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.RESP_ITEM_1, status_code=200, ) ret = self.api._request( 'GET', self.BASE_URL + '/qaz', session=self.sess, ) self.assertEqual(api_fakes.RESP_ITEM_1, ret.json()) self.assertIsNotNone(self.api.session) self.assertNotEqual(self.sess, self.api.session) class TestBaseAPIEndpointArg(api_fakes.TestSession): def test_baseapi_endpoint_no_endpoint(self): x_api = api.BaseAPI( session=self.sess, ) self.assertIsNotNone(x_api.session) self.assertEqual(self.sess, x_api.session) self.assertIsNone(x_api.endpoint) self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.RESP_ITEM_1, status_code=200, ) # Normal url self.assertRaises( ksa_exceptions.EndpointNotFound, x_api._request, 'GET', '/qaz', ) # No leading '/' url self.assertRaises( ksa_exceptions.EndpointNotFound, x_api._request, 'GET', 'qaz', ) # Extra leading '/' url self.assertRaises( ksa_exceptions.connection.UnknownConnectionError, x_api._request, 'GET', '//qaz', ) def test_baseapi_endpoint_no_extra(self): x_api = api.BaseAPI( session=self.sess, endpoint=self.BASE_URL, ) self.assertIsNotNone(x_api.session) self.assertEqual(self.sess, x_api.session) self.assertEqual(self.BASE_URL, x_api.endpoint) self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.RESP_ITEM_1, status_code=200, ) # Normal url ret = x_api._request('GET', '/qaz') self.assertEqual(api_fakes.RESP_ITEM_1, ret.json()) # No leading '/' url ret = x_api._request('GET', 'qaz') self.assertEqual(api_fakes.RESP_ITEM_1, ret.json()) # Extra leading '/' url ret = x_api._request('GET', '//qaz') self.assertEqual(api_fakes.RESP_ITEM_1, ret.json()) def test_baseapi_endpoint_extra(self): x_api = api.BaseAPI( session=self.sess, endpoint=self.BASE_URL + '/', ) self.assertIsNotNone(x_api.session) self.assertEqual(self.sess, x_api.session) self.assertEqual(self.BASE_URL, x_api.endpoint) self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.RESP_ITEM_1, status_code=200, ) # Normal url ret = x_api._request('GET', '/qaz') self.assertEqual(api_fakes.RESP_ITEM_1, ret.json()) # No leading '/' url ret = x_api._request('GET', 'qaz') self.assertEqual(api_fakes.RESP_ITEM_1, ret.json()) # Extra leading '/' url ret = x_api._request('GET', '//qaz') self.assertEqual(api_fakes.RESP_ITEM_1, ret.json()) class TestBaseAPIArgs(api_fakes.TestSession): def setUp(self): super(TestBaseAPIArgs, self).setUp() self.api = api.BaseAPI( session=self.sess, endpoint=self.BASE_URL, ) def test_baseapi_request_url_path(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.RESP_ITEM_1, status_code=200, ) ret = self.api._request('GET', '/qaz') self.assertEqual(api_fakes.RESP_ITEM_1, ret.json()) self.assertIsNotNone(self.api.session) self.assertEqual(self.sess, self.api.session) def test_baseapi_request_session(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.RESP_ITEM_1, status_code=200, ) new_session = session.Session() ret = self.api._request('GET', '/qaz', session=new_session) self.assertEqual(api_fakes.RESP_ITEM_1, ret.json()) self.assertIsNotNone(self.api.session) self.assertNotEqual(new_session, self.api.session) class TestBaseAPICreate(api_fakes.TestSession): def setUp(self): super(TestBaseAPICreate, self).setUp() self.api = api.BaseAPI( session=self.sess, endpoint=self.BASE_URL, ) def test_baseapi_create_post(self): self.requests_mock.register_uri( 'POST', self.BASE_URL + '/qaz', json=api_fakes.RESP_ITEM_1, status_code=202, ) ret = self.api.create('qaz') self.assertEqual(api_fakes.RESP_ITEM_1, ret) def test_baseapi_create_put(self): self.requests_mock.register_uri( 'PUT', self.BASE_URL + '/qaz', json=api_fakes.RESP_ITEM_1, status_code=202, ) ret = self.api.create('qaz', method='PUT') self.assertEqual(api_fakes.RESP_ITEM_1, ret) def test_baseapi_delete(self): self.requests_mock.register_uri( 'DELETE', self.BASE_URL + '/qaz', status_code=204, ) ret = self.api.delete('qaz') self.assertEqual(204, ret.status_code) class TestBaseAPIFind(api_fakes.TestSession): def setUp(self): super(TestBaseAPIFind, self).setUp() self.api = api.BaseAPI( session=self.sess, endpoint=self.BASE_URL, ) def test_baseapi_find(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz/1', json={'qaz': api_fakes.RESP_ITEM_1}, status_code=200, ) ret = self.api.find('qaz', '1') self.assertEqual(api_fakes.RESP_ITEM_1, ret) self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz/1', status_code=404, ) self.assertRaises( exceptions.NotFound, self.api.find, 'qaz', '1') def test_baseapi_find_attr_by_id(self): # All first requests (by name) will fail in this test self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz?name=1', json={'qaz': []}, status_code=200, ) self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz?id=1', json={'qaz': [api_fakes.RESP_ITEM_1]}, status_code=200, ) ret = self.api.find_attr('qaz', '1') self.assertEqual(api_fakes.RESP_ITEM_1, ret) # value not found self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz?name=0', json={'qaz': []}, status_code=200, ) self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz?id=0', json={'qaz': []}, status_code=200, ) self.assertRaises( exceptions.CommandError, self.api.find_attr, 'qaz', '0', ) # Attribute other than 'name' self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz?status=UP', json={'qaz': [api_fakes.RESP_ITEM_1]}, status_code=200, ) ret = self.api.find_attr('qaz', 'UP', attr='status') self.assertEqual(api_fakes.RESP_ITEM_1, ret) ret = self.api.find_attr('qaz', value='UP', attr='status') self.assertEqual(api_fakes.RESP_ITEM_1, ret) def test_baseapi_find_attr_by_name(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz?name=alpha', json={'qaz': [api_fakes.RESP_ITEM_1]}, status_code=200, ) ret = self.api.find_attr('qaz', 'alpha') self.assertEqual(api_fakes.RESP_ITEM_1, ret) # value not found self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz?name=0', json={'qaz': []}, status_code=200, ) self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz?id=0', json={'qaz': []}, status_code=200, ) self.assertRaises( exceptions.CommandError, self.api.find_attr, 'qaz', '0', ) # Attribute other than 'name' self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz?status=UP', json={'qaz': [api_fakes.RESP_ITEM_1]}, status_code=200, ) ret = self.api.find_attr('qaz', 'UP', attr='status') self.assertEqual(api_fakes.RESP_ITEM_1, ret) ret = self.api.find_attr('qaz', value='UP', attr='status') self.assertEqual(api_fakes.RESP_ITEM_1, ret) def test_baseapi_find_attr_path_resource(self): # Test resource different than path self.requests_mock.register_uri( 'GET', self.BASE_URL + '/wsx?name=1', json={'qaz': []}, status_code=200, ) self.requests_mock.register_uri( 'GET', self.BASE_URL + '/wsx?id=1', json={'qaz': [api_fakes.RESP_ITEM_1]}, status_code=200, ) ret = self.api.find_attr('wsx', '1', resource='qaz') self.assertEqual(api_fakes.RESP_ITEM_1, ret) def test_baseapi_find_bulk_none(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.LIST_RESP, status_code=200, ) ret = self.api.find_bulk('qaz') self.assertEqual(api_fakes.LIST_RESP, ret) # Verify headers arg does not interfere ret = self.api.find_bulk('qaz', headers={}) self.assertEqual(api_fakes.LIST_RESP, ret) def test_baseapi_find_bulk_one(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.LIST_RESP, status_code=200, ) ret = self.api.find_bulk('qaz', id='1') self.assertEqual([api_fakes.LIST_RESP[0]], ret) # Verify headers arg does not interfere with search ret = self.api.find_bulk('qaz', id='1', headers={}) self.assertEqual([api_fakes.LIST_RESP[0]], ret) ret = self.api.find_bulk('qaz', id='0') self.assertEqual([], ret) ret = self.api.find_bulk('qaz', name='beta') self.assertEqual([api_fakes.LIST_RESP[1]], ret) ret = self.api.find_bulk('qaz', error='bogus') self.assertEqual([], ret) def test_baseapi_find_bulk_two(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.LIST_RESP, status_code=200, ) ret = self.api.find_bulk('qaz', id='1', name='alpha') self.assertEqual([api_fakes.LIST_RESP[0]], ret) ret = self.api.find_bulk('qaz', id='1', name='beta') self.assertEqual([], ret) ret = self.api.find_bulk('qaz', id='1', error='beta') self.assertEqual([], ret) def test_baseapi_find_bulk_dict(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json={'qaz': api_fakes.LIST_RESP}, status_code=200, ) ret = self.api.find_bulk('qaz', id='1') self.assertEqual([api_fakes.LIST_RESP[0]], ret) class TestBaseAPIList(api_fakes.TestSession): def setUp(self): super(TestBaseAPIList, self).setUp() self.api = api.BaseAPI( session=self.sess, endpoint=self.BASE_URL, ) def test_baseapi_list_no_args(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz', json=api_fakes.LIST_RESP, status_code=204, ) ret = self.api.list('/qaz') self.assertEqual(api_fakes.LIST_RESP, ret) def test_baseapi_list_params(self): params = {'format': 'json'} self.requests_mock.register_uri( 'GET', self.BASE_URL + '?format=json', json=api_fakes.LIST_RESP, status_code=200, ) ret = self.api.list('', **params) self.assertEqual(api_fakes.LIST_RESP, ret) self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz?format=json', json=api_fakes.LIST_RESP, status_code=200, ) ret = self.api.list('qaz', **params) self.assertEqual(api_fakes.LIST_RESP, ret) def test_baseapi_list_body(self): self.requests_mock.register_uri( 'POST', self.BASE_URL + '/qaz', json=api_fakes.LIST_RESP, status_code=200, ) ret = self.api.list('qaz', body=api_fakes.LIST_BODY) self.assertEqual(api_fakes.LIST_RESP, ret) def test_baseapi_list_detailed(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz/details', json=api_fakes.LIST_RESP, status_code=200, ) ret = self.api.list('qaz', detailed=True) self.assertEqual(api_fakes.LIST_RESP, ret) def test_baseapi_list_filtered(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz?attr=value', json=api_fakes.LIST_RESP, status_code=200, ) ret = self.api.list('qaz', attr='value') self.assertEqual(api_fakes.LIST_RESP, ret) def test_baseapi_list_wrapped(self): self.requests_mock.register_uri( 'GET', self.BASE_URL + '/qaz?attr=value', json={'responses': api_fakes.LIST_RESP}, status_code=200, ) ret = self.api.list('qaz', attr='value') self.assertEqual({'responses': api_fakes.LIST_RESP}, ret) osc-lib-1.9.0/osc_lib/tests/test_logs.py0000666000175100017510000002075313227377263020253 0ustar zuulzuul00000000000000# 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 logging import mock from osc_lib import logs from osc_lib.tests import utils class TestContext(utils.TestCase): def test_log_level_from_options(self): opts = mock.Mock() opts.verbose_level = 0 self.assertEqual(logging.ERROR, logs.log_level_from_options(opts)) opts.verbose_level = 1 self.assertEqual(logging.WARNING, logs.log_level_from_options(opts)) opts.verbose_level = 2 self.assertEqual(logging.INFO, logs.log_level_from_options(opts)) opts.verbose_level = 3 self.assertEqual(logging.DEBUG, logs.log_level_from_options(opts)) def test_log_level_from_config(self): cfg = {'verbose_level': 0} self.assertEqual(logging.ERROR, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1} self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 2} self.assertEqual(logging.INFO, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 3} self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'critical'} self.assertEqual(logging.CRITICAL, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'error'} self.assertEqual(logging.ERROR, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'warning'} self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'info'} self.assertEqual(logging.INFO, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'debug'} self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'bogus'} self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'info', 'debug': True} self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg)) @mock.patch('warnings.simplefilter') def test_set_warning_filter(self, simplefilter): logs.set_warning_filter(logging.ERROR) simplefilter.assert_called_with("ignore") logs.set_warning_filter(logging.WARNING) simplefilter.assert_called_with("ignore") logs.set_warning_filter(logging.INFO) simplefilter.assert_called_with("once") class TestFileFormatter(utils.TestCase): def test_nothing(self): formatter = logs._FileFormatter() self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s %(message)s'), formatter.fmt) def test_options(self): class Opts(object): cloud = 'cloudy' os_project_name = 'projecty' username = 'usernamey' options = Opts() formatter = logs._FileFormatter(options=options) self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s [cloudy usernamey projecty] %(message)s'), formatter.fmt) def test_config(self): config = mock.Mock() config.config = {'cloud': 'cloudy'} config.auth = {'project_name': 'projecty', 'username': 'usernamey'} formatter = logs._FileFormatter(config=config) self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s [cloudy usernamey projecty] %(message)s'), formatter.fmt) class TestLogConfigurator(utils.TestCase): def setUp(self): super(TestLogConfigurator, self).setUp() self.options = mock.Mock() self.options.verbose_level = 1 self.options.log_file = None self.options.debug = False self.root_logger = mock.Mock() self.root_logger.setLevel = mock.Mock() self.root_logger.addHandler = mock.Mock() self.requests_log = mock.Mock() self.requests_log.setLevel = mock.Mock() self.cliff_log = mock.Mock() self.cliff_log.setLevel = mock.Mock() self.stevedore_log = mock.Mock() self.stevedore_log.setLevel = mock.Mock() self.iso8601_log = mock.Mock() self.iso8601_log.setLevel = mock.Mock() self.loggers = [ self.root_logger, self.requests_log, self.cliff_log, self.stevedore_log, self.iso8601_log] @mock.patch('logging.StreamHandler') @mock.patch('logging.getLogger') @mock.patch('osc_lib.logs.set_warning_filter') def test_init(self, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers console_logger = mock.Mock() console_logger.setFormatter = mock.Mock() console_logger.setLevel = mock.Mock() handle.return_value = console_logger configurator = logs.LogConfigurator(self.options) getLogger.assert_called_with('iso8601') # last call warning_filter.assert_called_with(logging.WARNING) self.root_logger.setLevel.assert_called_with(logging.DEBUG) self.root_logger.addHandler.assert_called_with(console_logger) self.requests_log.setLevel.assert_called_with(logging.ERROR) self.cliff_log.setLevel.assert_called_with(logging.ERROR) self.stevedore_log.setLevel.assert_called_with(logging.ERROR) self.iso8601_log.setLevel.assert_called_with(logging.ERROR) self.assertFalse(configurator.dump_trace) @mock.patch('logging.getLogger') @mock.patch('osc_lib.logs.set_warning_filter') def test_init_no_debug(self, warning_filter, getLogger): getLogger.side_effect = self.loggers self.options.debug = True configurator = logs.LogConfigurator(self.options) warning_filter.assert_called_with(logging.DEBUG) self.requests_log.setLevel.assert_called_with(logging.DEBUG) self.assertTrue(configurator.dump_trace) @mock.patch('logging.FileHandler') @mock.patch('logging.getLogger') @mock.patch('osc_lib.logs.set_warning_filter') @mock.patch('osc_lib.logs._FileFormatter') def test_init_log_file(self, formatter, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers self.options.log_file = '/tmp/log_file' file_logger = mock.Mock() file_logger.setFormatter = mock.Mock() file_logger.setLevel = mock.Mock() handle.return_value = file_logger mock_formatter = mock.Mock() formatter.return_value = mock_formatter logs.LogConfigurator(self.options) handle.assert_called_with(filename=self.options.log_file) self.root_logger.addHandler.assert_called_with(file_logger) file_logger.setFormatter.assert_called_with(mock_formatter) file_logger.setLevel.assert_called_with(logging.WARNING) @mock.patch('logging.FileHandler') @mock.patch('logging.getLogger') @mock.patch('osc_lib.logs.set_warning_filter') @mock.patch('osc_lib.logs._FileFormatter') def test_configure(self, formatter, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers configurator = logs.LogConfigurator(self.options) cloud_config = mock.Mock() config_log = '/tmp/config_log' cloud_config.config = { 'log_file': config_log, 'verbose_level': 1, 'log_level': 'info'} file_logger = mock.Mock() file_logger.setFormatter = mock.Mock() file_logger.setLevel = mock.Mock() handle.return_value = file_logger mock_formatter = mock.Mock() formatter.return_value = mock_formatter configurator.configure(cloud_config) warning_filter.assert_called_with(logging.INFO) handle.assert_called_with(filename=config_log) self.root_logger.addHandler.assert_called_with(file_logger) file_logger.setFormatter.assert_called_with(mock_formatter) file_logger.setLevel.assert_called_with(logging.INFO) self.assertFalse(configurator.dump_trace) osc-lib-1.9.0/osc_lib/tests/utils/0000775000175100017510000000000013227377613017024 5ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/tests/utils/test_utils.py0000666000175100017510000010150513227377263021602 0ustar zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # # 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 six import time import uuid from cliff import columns as cliff_columns import mock from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib.tests import fakes from osc_lib.tests import utils as test_utils from osc_lib import utils PASSWORD = "Pa$$w0rd" WASSPORD = "Wa$$p0rd" DROWSSAP = "dr0w$$aP" class FakeOddballResource(fakes.FakeResource): def get(self, attr): """get() is needed for utils.find_resource()""" if attr == 'id': return self.id elif attr == 'name': return self.name else: return None class TestUtils(test_utils.TestCase): def _get_test_items(self): item1 = {'a': 1, 'b': 2} item2 = {'a': 1, 'b': 3} item3 = {'a': 2, 'b': 2} item4 = {'a': 2, 'b': 1} return [item1, item2, item3, item4] def test_find_min_match_no_sort(self): items = self._get_test_items() sort_str = None flair = {} expect_items = items self.assertEqual( expect_items, list(utils.find_min_match(items, sort_str, **flair)), ) def test_find_min_match_no_flair(self): items = self._get_test_items() sort_str = 'b' flair = {} expect_items = [items[3], items[0], items[2], items[1]] self.assertEqual( expect_items, utils.find_min_match(items, sort_str, **flair), ) def test_find_min_match_a2(self): items = self._get_test_items() sort_str = 'b' flair = {'a': 2} expect_items = [items[3], items[2]] self.assertEqual( expect_items, utils.find_min_match(items, sort_str, **flair), ) def test_find_min_match_b2(self): items = self._get_test_items() sort_str = 'b' flair = {'b': 2} expect_items = [items[0], items[2], items[1]] self.assertEqual( expect_items, utils.find_min_match(items, sort_str, **flair), ) def test_find_min_match_b5(self): items = self._get_test_items() sort_str = 'b' flair = {'b': 5} expect_items = [] self.assertEqual( expect_items, utils.find_min_match(items, sort_str, **flair), ) def test_find_min_match_a2_b2(self): items = self._get_test_items() sort_str = 'b' flair = {'a': 2, 'b': 2} expect_items = [items[2]] self.assertEqual( expect_items, utils.find_min_match(items, sort_str, **flair), ) def test_get_password_good(self): with mock.patch("getpass.getpass", return_value=PASSWORD): mock_stdin = mock.Mock() mock_stdin.isatty = mock.Mock() mock_stdin.isatty.return_value = True self.assertEqual(PASSWORD, utils.get_password(mock_stdin)) def test_get_password_bad_once(self): answers = [PASSWORD, WASSPORD, DROWSSAP, DROWSSAP] with mock.patch("getpass.getpass", side_effect=answers): mock_stdin = mock.Mock() mock_stdin.isatty = mock.Mock() mock_stdin.isatty.return_value = True self.assertEqual(DROWSSAP, utils.get_password(mock_stdin)) def test_get_password_no_tty(self): mock_stdin = mock.Mock() mock_stdin.isatty = mock.Mock() mock_stdin.isatty.return_value = False self.assertRaises(exceptions.CommandError, utils.get_password, mock_stdin) def test_get_password_cntrl_d(self): with mock.patch("getpass.getpass", side_effect=EOFError()): mock_stdin = mock.Mock() mock_stdin.isatty = mock.Mock() mock_stdin.isatty.return_value = True self.assertRaises(exceptions.CommandError, utils.get_password, mock_stdin) def test_sort_items_with_one_key(self): items = self._get_test_items() sort_str = 'b' expect_items = [items[3], items[0], items[2], items[1]] self.assertEqual(expect_items, utils.sort_items(items, sort_str)) def test_sort_items_with_multiple_keys(self): items = self._get_test_items() sort_str = 'a,b' expect_items = [items[0], items[1], items[3], items[2]] self.assertEqual(expect_items, utils.sort_items(items, sort_str)) def test_sort_items_all_with_direction(self): items = self._get_test_items() sort_str = 'a:desc,b:desc' expect_items = [items[2], items[3], items[1], items[0]] self.assertEqual(expect_items, utils.sort_items(items, sort_str)) def test_sort_items_some_with_direction(self): items = self._get_test_items() sort_str = 'a,b:desc' expect_items = [items[1], items[0], items[2], items[3]] self.assertEqual(expect_items, utils.sort_items(items, sort_str)) def test_sort_items_with_object(self): item1 = mock.Mock(a=1, b=2) item2 = mock.Mock(a=1, b=3) item3 = mock.Mock(a=2, b=2) item4 = mock.Mock(a=2, b=1) items = [item1, item2, item3, item4] sort_str = 'b,a' expect_items = [item4, item1, item3, item2] self.assertEqual(expect_items, utils.sort_items(items, sort_str)) def test_sort_items_with_empty_key(self): items = self._get_test_items() sort_srt = '' self.assertEqual(items, utils.sort_items(items, sort_srt)) sort_srt = None self.assertEqual(items, utils.sort_items(items, sort_srt)) def test_sort_items_with_invalid_key(self): items = self._get_test_items() sort_str = 'c' self.assertRaises(exceptions.CommandError, utils.sort_items, items, sort_str) def test_sort_items_with_invalid_direction(self): items = self._get_test_items() sort_str = 'a:bad_dir' self.assertRaises(exceptions.CommandError, utils.sort_items, items, sort_str) def test_sort_items_with_different_type_exception(self): item1 = {'a': 2} item2 = {'a': 3} item3 = {'a': None} item4 = {'a': 1} items = [item1, item2, item3, item4] sort_str = 'a' expect_items = [item3, item4, item1, item2] if six.PY2: self.assertEqual(expect_items, utils.sort_items(items, sort_str)) else: self.assertRaises(TypeError, utils.sort_items, items, sort_str) def test_sort_items_with_different_type_int(self): item1 = {'a': 2} item2 = {'a': 3} item3 = {'a': None} item4 = {'a': 1} items = [item1, item2, item3, item4] sort_str = 'a' sort_type = int expect_items = [item3, item4, item1, item2] self.assertEqual(expect_items, utils.sort_items(items, sort_str, sort_type)) def test_sort_items_with_different_type_str(self): item1 = {'a': 'a'} item2 = {'a': None} item3 = {'a': '2'} item4 = {'a': 'b'} items = [item1, item2, item3, item4] sort_str = 'a' sort_type = str expect_items = [item3, item2, item1, item4] self.assertEqual(expect_items, utils.sort_items(items, sort_str, sort_type)) @mock.patch.object(time, 'sleep') def test_wait_for_delete_ok(self, mock_sleep): # Tests the normal flow that the resource is deleted with a 404 coming # back on the 2nd iteration of the wait loop. resource = mock.MagicMock(status='ACTIVE', progress=None) mock_get = mock.Mock(side_effect=[resource, exceptions.NotFound(404)]) manager = mock.MagicMock(get=mock_get) res_id = str(uuid.uuid4()) callback = mock.Mock() self.assertTrue(utils.wait_for_delete(manager, res_id, callback=callback)) mock_sleep.assert_called_once_with(5) callback.assert_called_once_with(0) @mock.patch.object(time, 'sleep') def test_wait_for_delete_timeout(self, mock_sleep): # Tests that we fail if the resource is not deleted before the timeout. resource = mock.MagicMock(status='ACTIVE') mock_get = mock.Mock(return_value=resource) manager = mock.MagicMock(get=mock_get) res_id = str(uuid.uuid4()) self.assertFalse(utils.wait_for_delete(manager, res_id, sleep_time=1, timeout=1)) mock_sleep.assert_called_once_with(1) @mock.patch.object(time, 'sleep') def test_wait_for_delete_error(self, mock_sleep): # Tests that we fail if the resource goes to error state while waiting. resource = mock.MagicMock(status='ERROR') mock_get = mock.Mock(return_value=resource) manager = mock.MagicMock(get=mock_get) res_id = str(uuid.uuid4()) self.assertFalse(utils.wait_for_delete(manager, res_id)) mock_sleep.assert_not_called() @mock.patch.object(time, 'sleep') def test_wait_for_delete_error_with_overrides(self, mock_sleep): # Tests that we fail if the resource is my_status=failed resource = mock.MagicMock(my_status='FAILED') mock_get = mock.Mock(return_value=resource) manager = mock.MagicMock(get=mock_get) res_id = str(uuid.uuid4()) self.assertFalse(utils.wait_for_delete(manager, res_id, status_field='my_status', error_status=['failed'])) mock_sleep.assert_not_called() @mock.patch.object(time, 'sleep') def test_wait_for_delete_error_with_overrides_exception(self, mock_sleep): # Tests that we succeed if the resource is specific exception mock_get = mock.Mock(side_effect=Exception) manager = mock.MagicMock(get=mock_get) res_id = str(uuid.uuid4()) self.assertTrue(utils.wait_for_delete(manager, res_id, exception_name=['Exception'])) mock_sleep.assert_not_called() @mock.patch.object(time, 'sleep') def test_wait_for_status_ok(self, mock_sleep): # Tests the normal flow that the resource is status=active resource = mock.MagicMock(status='ACTIVE') status_f = mock.Mock(return_value=resource) res_id = str(uuid.uuid4()) self.assertTrue(utils.wait_for_status(status_f, res_id,)) mock_sleep.assert_not_called() @mock.patch.object(time, 'sleep') def test_wait_for_status_ok_with_overrides(self, mock_sleep): # Tests the normal flow that the resource is status=complete resource = mock.MagicMock(my_status='COMPLETE') status_f = mock.Mock(return_value=resource) res_id = str(uuid.uuid4()) self.assertTrue(utils.wait_for_status(status_f, res_id, status_field='my_status', success_status=['complete'])) mock_sleep.assert_not_called() @mock.patch.object(time, 'sleep') def test_wait_for_status_error(self, mock_sleep): # Tests that we fail if the resource is status=error resource = mock.MagicMock(status='ERROR') status_f = mock.Mock(return_value=resource) res_id = str(uuid.uuid4()) self.assertFalse(utils.wait_for_status(status_f, res_id)) mock_sleep.assert_not_called() @mock.patch.object(time, 'sleep') def test_wait_for_status_error_with_overrides(self, mock_sleep): # Tests that we fail if the resource is my_status=failed resource = mock.MagicMock(my_status='FAILED') status_f = mock.Mock(return_value=resource) res_id = str(uuid.uuid4()) self.assertFalse(utils.wait_for_status(status_f, res_id, status_field='my_status', error_status=['failed'])) mock_sleep.assert_not_called() def test_build_kwargs_dict_value_set(self): self.assertEqual({'arg_bla': 'bla'}, utils.build_kwargs_dict('arg_bla', 'bla')) def test_build_kwargs_dict_value_None(self): self.assertEqual({}, utils.build_kwargs_dict('arg_bla', None)) def test_build_kwargs_dict_value_empty_str(self): self.assertEqual({}, utils.build_kwargs_dict('arg_bla', '')) def test_is_ascii_bytes(self): self.assertFalse(utils.is_ascii(b'\xe2')) def test_is_ascii_string(self): self.assertFalse(utils.is_ascii(u'\u2665')) def test_format_size(self): self.assertEqual("999", utils.format_size(999)) self.assertEqual("100K", utils.format_size(100000)) self.assertEqual("2M", utils.format_size(2000000)) self.assertEqual( "16.4M", utils.format_size(16361280) ) self.assertEqual( "1.6G", utils.format_size(1576395005) ) self.assertEqual("0", utils.format_size(None)) def test_backward_compat_col_lister(self): fake_col_headers = ['ID', 'Name', 'Size'] columns = ['Display Name'] column_map = {'Display Name': 'Name'} results = utils.backward_compat_col_lister(fake_col_headers, columns, column_map) self.assertIsInstance(results, list) self.assertIn('Display Name', results) self.assertNotIn('Name', results) self.assertIn('ID', results) self.assertIn('Size', results) def test_backward_compat_col_lister_no_specify_column(self): fake_col_headers = ['ID', 'Name', 'Size'] columns = [] column_map = {'Display Name': 'Name'} results = utils.backward_compat_col_lister(fake_col_headers, columns, column_map) self.assertIsInstance(results, list) self.assertNotIn('Display Name', results) self.assertIn('Name', results) self.assertIn('ID', results) self.assertIn('Size', results) def test_backward_compat_col_lister_with_tuple_headers(self): fake_col_headers = ('ID', 'Name', 'Size') columns = ['Display Name'] column_map = {'Display Name': 'Name'} results = utils.backward_compat_col_lister(fake_col_headers, columns, column_map) self.assertIsInstance(results, list) self.assertIn('Display Name', results) self.assertNotIn('Name', results) self.assertIn('ID', results) self.assertIn('Size', results) def test_backward_compat_col_showone(self): fake_object = {'id': 'fake-id', 'name': 'fake-name', 'size': 'fake-size'} columns = ['display_name'] column_map = {'display_name': 'name'} results = utils.backward_compat_col_showone(fake_object, columns, column_map) self.assertIsInstance(results, dict) self.assertIn('display_name', results) self.assertIn('id', results) self.assertNotIn('name', results) self.assertIn('size', results) def test_backward_compat_col_showone_no_specify_column(self): fake_object = {'id': 'fake-id', 'name': 'fake-name', 'size': 'fake-size'} columns = [] column_map = {'display_name': 'name'} results = utils.backward_compat_col_showone(fake_object, columns, column_map) self.assertIsInstance(results, dict) self.assertNotIn('display_name', results) self.assertIn('id', results) self.assertIn('name', results) self.assertIn('size', results) def _test_get_item_properties_with_formatter(self, formatters): names = ('id', 'attr') item = fakes.FakeResource(info={'id': 'fake-id', 'attr': ['a', 'b']}) res_id, res_attr = utils.get_item_properties(item, names, formatters=formatters) self.assertEqual('fake-id', res_id) return res_attr def test_get_item_properties_with_format_func(self): formatters = {'attr': utils.format_list} res_attr = self._test_get_item_properties_with_formatter(formatters) self.assertEqual(utils.format_list(['a', 'b']), res_attr) def test_get_item_properties_with_formattable_column(self): formatters = {'attr': format_columns.ListColumn} res_attr = self._test_get_item_properties_with_formatter(formatters) self.assertIsInstance(res_attr, format_columns.ListColumn) def _test_get_dict_properties_with_formatter(self, formatters): names = ('id', 'attr') item = {'id': 'fake-id', 'attr': ['a', 'b']} res_id, res_attr = utils.get_dict_properties(item, names, formatters=formatters) self.assertEqual('fake-id', res_id) return res_attr def test_get_dict_properties_with_format_func(self): formatters = {'attr': utils.format_list} res_attr = self._test_get_dict_properties_with_formatter(formatters) self.assertEqual(utils.format_list(['a', 'b']), res_attr) def test_get_dict_properties_with_formattable_column(self): formatters = {'attr': format_columns.ListColumn} res_attr = self._test_get_dict_properties_with_formatter(formatters) self.assertIsInstance(res_attr, format_columns.ListColumn) def _test_calculate_header_and_attrs(self, parsed_args_columns, expected_headers, expected_attrs): column_headers = ('ID', 'Name', 'Fixed IP Addresses') columns = ('id', 'name', 'fixed_ips') parsed_args = mock.Mock() parsed_args.columns = parsed_args_columns ret_headers, ret_attrs = utils.calculate_header_and_attrs( column_headers, columns, parsed_args) self.assertEqual(expected_headers, ret_headers) self.assertEqual(expected_attrs, ret_attrs) if parsed_args_columns: self.assertEqual(expected_headers, parsed_args.columns) else: self.assertFalse(parsed_args.columns) def test_calculate_header_and_attrs_without_column_arg(self): self._test_calculate_header_and_attrs( [], ('ID', 'Name', 'Fixed IP Addresses'), ('id', 'name', 'fixed_ips')) def test_calculate_header_and_attrs_with_known_columns(self): self._test_calculate_header_and_attrs( ['Name', 'ID'], ['Name', 'ID'], ['name', 'id']) def test_calculate_header_and_attrs_with_unknown_columns(self): self._test_calculate_header_and_attrs( ['Name', 'ID', 'device_id'], ['Name', 'ID', 'device_id'], ['name', 'id', 'device_id']) def test_calculate_header_and_attrs_with_attrname_columns(self): self._test_calculate_header_and_attrs( ['name', 'id', 'device_id'], ['Name', 'ID', 'device_id'], ['name', 'id', 'device_id']) class NoUniqueMatch(Exception): pass class TestFindResource(test_utils.TestCase): def setUp(self): super(TestFindResource, self).setUp() self.name = 'legos' self.expected = mock.Mock() self.manager = mock.Mock() self.manager.resource_class = mock.Mock() self.manager.resource_class.__name__ = 'lego' def test_find_resource_get_int(self): self.manager.get = mock.Mock(return_value=self.expected) result = utils.find_resource(self.manager, 1) self.assertEqual(self.expected, result) self.manager.get.assert_called_with(1) def test_find_resource_get_int_string(self): self.manager.get = mock.Mock(return_value=self.expected) result = utils.find_resource(self.manager, "2") self.assertEqual(self.expected, result) self.manager.get.assert_called_with("2") def test_find_resource_get_name_and_domain(self): name = 'admin' domain_id = '30524568d64447fbb3fa8b7891c10dd6' # NOTE(stevemar): we need an iterable side-effect because the same # function (manager.get()) is used twice, the first time an exception # will happen, then the result will be found, but only after using # the domain ID as a query arg side_effect = [Exception('Boom!'), self.expected] self.manager.get = mock.Mock(side_effect=side_effect) result = utils.find_resource(self.manager, name, domain_id=domain_id) self.assertEqual(self.expected, result) self.manager.get.assert_called_with(name, domain_id=domain_id) def test_find_resource_get_uuid(self): uuid = '9a0dc2a0-ad0d-11e3-a5e2-0800200c9a66' self.manager.get = mock.Mock(return_value=self.expected) result = utils.find_resource(self.manager, uuid) self.assertEqual(self.expected, result) self.manager.get.assert_called_with(uuid) def test_find_resource_get_whatever(self): self.manager.get = mock.Mock(return_value=self.expected) result = utils.find_resource(self.manager, 'whatever') self.assertEqual(self.expected, result) self.manager.get.assert_called_with('whatever') def test_find_resource_find(self): self.manager.get = mock.Mock(side_effect=Exception('Boom!')) self.manager.find = mock.Mock(return_value=self.expected) result = utils.find_resource(self.manager, self.name) self.assertEqual(self.expected, result) self.manager.get.assert_called_with(self.name) self.manager.find.assert_called_with(name=self.name) def test_find_resource_find_not_found(self): self.manager.get = mock.Mock(side_effect=Exception('Boom!')) self.manager.find = mock.Mock( side_effect=exceptions.NotFound(404, "2") ) result = self.assertRaises(exceptions.CommandError, utils.find_resource, self.manager, self.name) self.assertEqual("No lego with a name or ID of 'legos' exists.", str(result)) self.manager.get.assert_called_with(self.name) self.manager.find.assert_called_with(name=self.name) def test_find_resource_list_forbidden(self): self.manager.get = mock.Mock(side_effect=Exception('Boom!')) self.manager.find = mock.Mock(side_effect=Exception('Boom!')) self.manager.list = mock.Mock( side_effect=exceptions.Forbidden(403) ) self.assertRaises(exceptions.Forbidden, utils.find_resource, self.manager, self.name) self.manager.list.assert_called_with() def test_find_resource_find_no_unique(self): self.manager.get = mock.Mock(side_effect=Exception('Boom!')) self.manager.find = mock.Mock(side_effect=NoUniqueMatch()) result = self.assertRaises(exceptions.CommandError, utils.find_resource, self.manager, self.name) self.assertEqual("More than one lego exists with the name 'legos'.", str(result)) self.manager.get.assert_called_with(self.name) self.manager.find.assert_called_with(name=self.name) def test_find_resource_silly_resource(self): # We need a resource with no resource_class for this test, start fresh self.manager = mock.Mock() self.manager.get = mock.Mock(side_effect=Exception('Boom!')) self.manager.find = mock.Mock( side_effect=AttributeError( "'Controller' object has no attribute 'find'", ) ) silly_resource = FakeOddballResource( None, {'id': '12345', 'name': self.name}, loaded=True, ) self.manager.list = mock.Mock( return_value=[silly_resource, ], ) result = utils.find_resource(self.manager, self.name) self.assertEqual(silly_resource, result) self.manager.get.assert_called_with(self.name) self.manager.find.assert_called_with(name=self.name) def test_find_resource_silly_resource_not_found(self): # We need a resource with no resource_class for this test, start fresh self.manager = mock.Mock() self.manager.get = mock.Mock(side_effect=Exception('Boom!')) self.manager.find = mock.Mock( side_effect=AttributeError( "'Controller' object has no attribute 'find'", ) ) self.manager.list = mock.Mock(return_value=[]) result = self.assertRaises(exceptions.CommandError, utils.find_resource, self.manager, self.name) self.assertEqual("Could not find resource legos", str(result)) self.manager.get.assert_called_with(self.name) self.manager.find.assert_called_with(name=self.name) def test_find_resource_silly_resource_no_unique_match(self): # We need a resource with no resource_class for this test, start fresh self.manager = mock.Mock() self.manager.get = mock.Mock(side_effect=Exception('Boom!')) self.manager.find = mock.Mock( side_effect=AttributeError( "'Controller' object has no attribute 'find'", ) ) silly_resource = FakeOddballResource( None, {'id': '12345', 'name': self.name}, loaded=True, ) silly_resource_same = FakeOddballResource( None, {'id': 'abcde', 'name': self.name}, loaded=True, ) self.manager.list = mock.Mock(return_value=[silly_resource, silly_resource_same]) result = self.assertRaises(exceptions.CommandError, utils.find_resource, self.manager, self.name) self.assertEqual("More than one resource exists " "with the name or ID 'legos'.", str(result)) self.manager.get.assert_called_with(self.name) self.manager.find.assert_called_with(name=self.name) def test_format_dict(self): expected = "a='b', c='d', e='f'" self.assertEqual(expected, utils.format_dict({'a': 'b', 'c': 'd', 'e': 'f'})) self.assertEqual(expected, utils.format_dict({'e': 'f', 'c': 'd', 'a': 'b'})) self.assertIsNone(utils.format_dict(None)) def test_format_dict_of_list(self): expected = "a=a1, a2; b=b1, b2; c=c1, c2; e=" self.assertEqual(expected, utils.format_dict_of_list({'a': ['a2', 'a1'], 'b': ['b2', 'b1'], 'c': ['c1', 'c2'], 'd': None, 'e': []}) ) self.assertEqual(expected, utils.format_dict_of_list({'c': ['c1', 'c2'], 'a': ['a2', 'a1'], 'b': ['b2', 'b1'], 'e': []}) ) self.assertIsNone(utils.format_dict_of_list(None)) def test_format_dict_of_list_with_separator(self): expected = "a=a1, a2\nb=b1, b2\nc=c1, c2\ne=" self.assertEqual(expected, utils.format_dict_of_list({'a': ['a2', 'a1'], 'b': ['b2', 'b1'], 'c': ['c1', 'c2'], 'd': None, 'e': []}, separator='\n') ) self.assertEqual(expected, utils.format_dict_of_list({'c': ['c1', 'c2'], 'a': ['a2', 'a1'], 'b': ['b2', 'b1'], 'e': []}, separator='\n') ) self.assertIsNone(utils.format_dict_of_list(None, separator='\n')) def test_format_list(self): expected = 'a, b, c' self.assertEqual(expected, utils.format_list(['a', 'b', 'c'])) self.assertEqual(expected, utils.format_list(['c', 'b', 'a'])) self.assertIsNone(utils.format_list(None)) def test_format_list_of_dicts(self): expected = "a='b', c='d'\ne='f'" sorted_data = [{'a': 'b', 'c': 'd'}, {'e': 'f'}] unsorted_data = [{'c': 'd', 'a': 'b'}, {'e': 'f'}] self.assertEqual(expected, utils.format_list_of_dicts(sorted_data)) self.assertEqual(expected, utils.format_list_of_dicts(unsorted_data)) self.assertEqual('', utils.format_list_of_dicts([])) self.assertEqual('', utils.format_list_of_dicts([{}])) self.assertIsNone(utils.format_list_of_dicts(None)) def test_format_list_separator(self): expected = 'a\nb\nc' actual_pre_sorted = utils.format_list(['a', 'b', 'c'], separator='\n') actual_unsorted = utils.format_list(['c', 'b', 'a'], separator='\n') self.assertEqual(expected, actual_pre_sorted) self.assertEqual(expected, actual_unsorted) class TestAssertItemEqual(test_utils.TestCommand): def test_assert_normal_item(self): expected = ['a', 'b', 'c'] actual = ['a', 'b', 'c'] self.assertItemEqual(expected, actual) def test_assert_item_with_formattable_columns(self): expected = [format_columns.DictColumn({'a': 1, 'b': 2}), format_columns.ListColumn(['x', 'y', 'z'])] actual = [format_columns.DictColumn({'a': 1, 'b': 2}), format_columns.ListColumn(['x', 'y', 'z'])] self.assertItemEqual(expected, actual) def test_assert_item_different_length(self): expected = ['a', 'b', 'c'] actual = ['a', 'b'] self.assertRaises(AssertionError, self.assertItemEqual, expected, actual) def test_assert_item_formattable_columns_vs_legacy_formatter(self): expected = [format_columns.DictColumn({'a': 1, 'b': 2}), format_columns.ListColumn(['x', 'y', 'z'])] actual = [utils.format_dict({'a': 1, 'b': 2}), utils.format_list(['x', 'y', 'z'])] self.assertRaises(AssertionError, self.assertItemEqual, expected, actual) def test_assert_item_different_formattable_columns(self): class ExceptionColumn(cliff_columns.FormattableColumn): def human_readable(self): raise Exception('always fail') expected = [format_columns.DictColumn({'a': 1, 'b': 2})] actual = [ExceptionColumn({'a': 1, 'b': 2})] # AssertionError is a subclass of Exception # so raising AssertionError ensures ExceptionColumn.human_readable() # is not called. self.assertRaises(AssertionError, self.assertItemEqual, expected, actual) def test_assert_list_item(self): expected = [ ['a', 'b', 'c'], [format_columns.DictColumn({'a': 1, 'b': 2}), format_columns.ListColumn(['x', 'y', 'z'])] ] actual = [ ['a', 'b', 'c'], [format_columns.DictColumn({'a': 1, 'b': 2}), format_columns.ListColumn(['x', 'y', 'z'])] ] self.assertListItemEqual(expected, actual) osc-lib-1.9.0/osc_lib/tests/utils/__init__.py0000666000175100017510000003376313227377263021154 0ustar zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # Copyright 2013 Nebula Inc. # # 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 copy import json as jsonutils import mock import os from cliff import columns as cliff_columns import fixtures from keystoneauth1 import loading try: from openstack.config import cloud_config from openstack.config import defaults except ImportError: from os_client_config import cloud_config from os_client_config import defaults from oslo_utils import importutils from requests_mock.contrib import fixture import testtools from osc_lib import clientmanager from osc_lib import shell from osc_lib.tests import fakes # NOTE(dtroyer): Attempt the import to detect if the SDK installed is new # enough to contain the os_client_config code. If so, use # that path for mocks. CONFIG_MOCK_BASE = "openstack.config.loader" try: from openstack.config import loader as config # noqa except ImportError: # Fall back to os-client-config CONFIG_MOCK_BASE = "os_client_config.config" def fake_execute(shell, cmd): """Pretend to execute shell commands.""" return shell.run(cmd.split()) def make_shell(shell_class=None): """Create a new command shell and mock out some bits.""" if shell_class is None: shell_class = shell.OpenStackShell _shell = shell_class() _shell.command_manager = mock.Mock() # _shell.cloud = mock.Mock() return _shell def opt2attr(opt): if opt.startswith('--os-'): attr = opt[5:] elif opt.startswith('--'): attr = opt[2:] else: attr = opt return attr.lower().replace('-', '_') def opt2env(opt): return opt[2:].upper().replace('-', '_') class EnvFixture(fixtures.Fixture): """Environment Fixture. This fixture replaces os.environ with provided env or an empty env. """ def __init__(self, env=None): self.new_env = env or {} def _setUp(self): self.orig_env, os.environ = os.environ, self.new_env self.addCleanup(self.revert) def revert(self): os.environ = self.orig_env class ParserException(Exception): pass class TestCase(testtools.TestCase): def setUp(self): testtools.TestCase.setUp(self) if (os.environ.get("OS_STDOUT_CAPTURE") == "True" or os.environ.get("OS_STDOUT_CAPTURE") == "1"): stdout = self.useFixture(fixtures.StringStream("stdout")).stream self.useFixture(fixtures.MonkeyPatch("sys.stdout", stdout)) if (os.environ.get("OS_STDERR_CAPTURE") == "True" or os.environ.get("OS_STDERR_CAPTURE") == "1"): stderr = self.useFixture(fixtures.StringStream("stderr")).stream self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr)) def assertNotCalled(self, m, msg=None): """Assert a function was not called""" if m.called: if not msg: msg = 'method %s should not have been called' % m self.fail(msg) class TestCommand(TestCase): """Test cliff command classes""" def setUp(self): super(TestCommand, self).setUp() # Build up a fake app self.fake_stdout = fakes.FakeStdout() self.fake_log = fakes.FakeLog() self.app = fakes.FakeApp(self.fake_stdout, self.fake_log) self.app.client_manager = fakes.FakeClientManager() def check_parser(self, cmd, args, verify_args): cmd_parser = cmd.get_parser('check_parser') try: parsed_args = cmd_parser.parse_args(args) except SystemExit: raise ParserException("Argument parse failed") for av in verify_args: attr, value = av if attr: self.assertIn(attr, parsed_args) self.assertEqual(value, getattr(parsed_args, attr)) return parsed_args def assertItemEqual(self, expected, actual): """Compare item considering formattable columns. This method compares an observed item to an expected item column by column. If a column is a formattable column, observed and expected columns are compared using human_readable() and machine_readable(). """ self.assertEqual(len(expected), len(actual)) for col_expected, col_actual in zip(expected, actual): if isinstance(col_expected, cliff_columns.FormattableColumn): self.assertIsInstance(col_actual, col_expected.__class__) self.assertEqual(col_expected.human_readable(), col_actual.human_readable()) self.assertEqual(col_expected.machine_readable(), col_actual.machine_readable()) else: self.assertEqual(col_expected, col_actual) def assertListItemEqual(self, expected, actual): """Compare a list of items considering formattable columns. Each pair of observed and expected items are compared using assertItemEqual() method. """ self.assertEqual(len(expected), len(actual)) for item_expected, item_actual in zip(expected, actual): self.assertItemEqual(item_expected, item_actual) class TestClientManager(TestCase): """ClientManager class test framework""" default_password_auth = { 'auth_url': fakes.AUTH_URL, 'username': fakes.USERNAME, 'password': fakes.PASSWORD, 'project_name': fakes.PROJECT_NAME, } default_token_auth = { 'auth_url': fakes.AUTH_URL, 'token': fakes.AUTH_TOKEN, } def setUp(self): super(TestClientManager, self).setUp() self.mock = mock.Mock() self.requests = self.useFixture(fixture.Fixture()) # fake v2password token retrieval self.stub_auth(json=fakes.TEST_RESPONSE_DICT) # fake token and token_endpoint retrieval self.stub_auth(json=fakes.TEST_RESPONSE_DICT, url='/'.join([fakes.AUTH_URL, 'v2.0/tokens'])) # fake v3password token retrieval self.stub_auth(json=fakes.TEST_RESPONSE_DICT_V3, url='/'.join([fakes.AUTH_URL, 'v3/auth/tokens'])) # fake password token retrieval self.stub_auth(json=fakes.TEST_RESPONSE_DICT_V3, url='/'.join([fakes.AUTH_URL, 'auth/tokens'])) # fake password version endpoint discovery self.stub_auth(json=fakes.TEST_VERSIONS, url=fakes.AUTH_URL, verb='GET') # Mock the auth plugin self.auth_mock = mock.Mock() def stub_auth(self, json=None, url=None, verb=None, **kwargs): subject_token = fakes.AUTH_TOKEN base_url = fakes.AUTH_URL if json: text = jsonutils.dumps(json) headers = { 'X-Subject-Token': subject_token, 'Content-Type': 'application/json', } if not url: url = '/'.join([base_url, 'tokens']) url = url.replace("/?", "?") if not verb: verb = 'POST' self.requests.register_uri( verb, url, headers=headers, text=text, ) def _clientmanager_class(self): """Allow subclasses to override the ClientManager class""" return clientmanager.ClientManager def _make_clientmanager( self, auth_args=None, config_args=None, identity_api_version=None, auth_plugin_name=None, auth_required=None, ): if identity_api_version is None: identity_api_version = '2.0' if auth_plugin_name is None: auth_plugin_name = 'password' if auth_plugin_name.endswith('password'): auth_dict = copy.deepcopy(self.default_password_auth) elif auth_plugin_name.endswith('token'): auth_dict = copy.deepcopy(self.default_token_auth) else: auth_dict = {} if auth_args is not None: auth_dict = auth_args cli_options = defaults.get_defaults() cli_options.update({ 'auth_type': auth_plugin_name, 'auth': auth_dict, 'interface': fakes.INTERFACE, 'region_name': fakes.REGION_NAME, # 'workflow_api_version': '2', }) if config_args is not None: cli_options.update(config_args) loader = loading.get_plugin_loader(auth_plugin_name) auth_plugin = loader.load_from_options(**auth_dict) client_manager = self._clientmanager_class()( cli_options=cloud_config.CloudConfig( name='t1', region='1', config=cli_options, auth_plugin=auth_plugin, ), api_version={ 'identity': identity_api_version, }, ) client_manager._auth_required = auth_required is True client_manager.setup_auth() client_manager.auth_ref self.assertEqual( auth_plugin_name, client_manager.auth_plugin_name, ) return client_manager class TestShell(TestCase): # Full name of the OpenStackShell class to test (cliff.app.App subclass) shell_class_name = "osc_lib.shell.OpenStackShell" def setUp(self): super(TestShell, self).setUp() self.shell_class = importutils.import_class(self.shell_class_name) self.cmd_patch = mock.patch(self.shell_class_name + ".run_subcommand") self.cmd_save = self.cmd_patch.start() self.addCleanup(self.cmd_patch.stop) self.app = mock.Mock("Test Shell") def _assert_initialize_app_arg(self, cmd_options, default_args): """Check the args passed to initialize_app() The argv argument to initialize_app() is the remainder from parsing global options declared in both cliff.app and osc_lib.OpenStackShell build_option_parser(). Any global options passed on the command line should not be in argv but in _shell.options. """ with mock.patch( self.shell_class_name + ".initialize_app", self.app, ): _shell = make_shell(shell_class=self.shell_class) _cmd = cmd_options + " module list" fake_execute(_shell, _cmd) self.app.assert_called_with(["module", "list"]) for k in default_args.keys(): self.assertEqual( default_args[k], vars(_shell.options)[k], "%s does not match" % k, ) def _assert_cloud_config_arg(self, cmd_options, default_args): """Check the args passed to cloud_config.get_one_cloud() The argparse argument to get_one_cloud() is an argparse.Namespace object that contains all of the options processed to this point in initialize_app(). """ cloud = mock.Mock(name="cloudy") cloud.config = {} self.occ_get_one = mock.Mock(return_value=cloud) with mock.patch( CONFIG_MOCK_BASE + ".OpenStackConfig.get_one_cloud", self.occ_get_one, ): _shell = make_shell(shell_class=self.shell_class) _cmd = cmd_options + " module list" fake_execute(_shell, _cmd) self.app.assert_called_with(["module", "list"]) opts = self.occ_get_one.call_args[1]['argparse'] for k in default_args.keys(): self.assertEqual( default_args[k], vars(opts)[k], "%s does not match" % k, ) def _test_options_init_app(self, test_opts): """Test options on the command line""" for opt in test_opts.keys(): if not test_opts[opt][1]: continue key = opt2attr(opt) if isinstance(test_opts[opt][0], str): cmd = opt + " " + test_opts[opt][0] else: cmd = opt kwargs = { key: test_opts[opt][0], } self._assert_initialize_app_arg(cmd, kwargs) def _test_env_init_app(self, test_opts): """Test options in the environment""" for opt in test_opts.keys(): if not test_opts[opt][2]: continue key = opt2attr(opt) kwargs = { key: test_opts[opt][0], } env = { opt2env(opt): test_opts[opt][0], } os.environ = env.copy() self._assert_initialize_app_arg("", kwargs) def _test_options_get_one_cloud(self, test_opts): """Test options sent "to openstack.config""" for opt in test_opts.keys(): if not test_opts[opt][1]: continue key = opt2attr(opt) if isinstance(test_opts[opt][0], str): cmd = opt + " " + test_opts[opt][0] else: cmd = opt kwargs = { key: test_opts[opt][0], } self._assert_cloud_config_arg(cmd, kwargs) def _test_env_get_one_cloud(self, test_opts): """Test environment options sent "to openstack.config""" for opt in test_opts.keys(): if not test_opts[opt][2]: continue key = opt2attr(opt) kwargs = { key: test_opts[opt][0], } env = { opt2env(opt): test_opts[opt][0], } os.environ = env.copy() self._assert_cloud_config_arg("", kwargs) osc-lib-1.9.0/osc_lib/tests/utils/test_columns.py0000666000175100017510000000457413227377263022132 0ustar zuulzuul00000000000000# 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. # from osc_lib.tests import utils as test_utils from osc_lib.utils import columns as column_utils class TestColumnUtils(test_utils.TestCase): def test_get_column_definitions(self): attr_map = ( ('id', 'ID', column_utils.LIST_BOTH), ('tenant_id', 'Project', column_utils.LIST_LONG_ONLY), ('name', 'Name', column_utils.LIST_BOTH), ('summary', 'Summary', column_utils.LIST_SHORT_ONLY), ) headers, columns = column_utils.get_column_definitions( attr_map, long_listing=False) self.assertEqual(['id', 'name', 'summary'], columns) self.assertEqual(['ID', 'Name', 'Summary'], headers) def test_get_column_definitions_long(self): attr_map = ( ('id', 'ID', column_utils.LIST_BOTH), ('tenant_id', 'Project', column_utils.LIST_LONG_ONLY), ('name', 'Name', column_utils.LIST_BOTH), ('summary', 'Summary', column_utils.LIST_SHORT_ONLY), ) headers, columns = column_utils.get_column_definitions( attr_map, long_listing=True) self.assertEqual(['id', 'tenant_id', 'name'], columns) self.assertEqual(['ID', 'Project', 'Name'], headers) def test_get_columns(self): item = { 'id': 'test-id', 'tenant_id': 'test-tenant_id', # 'name' is not included 'foo': 'bar', # unknown attribute } attr_map = ( ('id', 'ID', column_utils.LIST_BOTH), ('tenant_id', 'Project', column_utils.LIST_LONG_ONLY), ('name', 'Name', column_utils.LIST_BOTH), ) columns, display_names = column_utils.get_columns(item, attr_map) self.assertEqual(tuple(['id', 'tenant_id', 'foo']), columns) self.assertEqual(tuple(['ID', 'Project', 'foo']), display_names) osc-lib-1.9.0/osc_lib/tests/test_clientmanager.py0000666000175100017510000003221113227377263022110 0ustar zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # # 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 copy import mock from keystoneauth1.access import service_catalog from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1.identity import generic as generic_plugin from keystoneauth1.identity.v3 import k2k from keystoneauth1 import loading from keystoneauth1 import token_endpoint try: from openstack.config import cloud_config _occ_in_sdk = True except ImportError: from os_client_config import cloud_config _occ_in_sdk = False from openstack import connection from osc_lib.api import auth from osc_lib import clientmanager from osc_lib import exceptions as exc from osc_lib.tests import fakes from osc_lib.tests import utils AUTH_REF = {'version': 'v2.0'} AUTH_REF.update(fakes.TEST_RESPONSE_DICT['access']) SERVICE_CATALOG = service_catalog.ServiceCatalogV2(AUTH_REF) AUTH_DICT = { 'auth_url': fakes.AUTH_URL, 'username': fakes.USERNAME, 'password': fakes.PASSWORD, 'project_name': fakes.PROJECT_NAME } # This is deferred in api.auth but we need it here... auth.get_options_list() class Container(object): attr = clientmanager.ClientCache(lambda x: object()) buggy_attr = clientmanager.ClientCache(lambda x: x.foo) def __init__(self): pass class TestClientCache(utils.TestCase): def test_singleton(self): # NOTE(dtroyer): Verify that the ClientCache descriptor only invokes # the factory one time and always returns the same value after that. c = Container() self.assertEqual(c.attr, c.attr) def test_attribute_error_propagates(self): c = Container() err = self.assertRaises(exc.PluginAttributeError, getattr, c, 'buggy_attr') self.assertNotIsInstance(err, AttributeError) self.assertEqual("'Container' object has no attribute 'foo'", str(err)) class TestClientManager(utils.TestClientManager): def test_client_manager_admin_token(self): token_auth = { 'endpoint': fakes.AUTH_URL, 'token': fakes.AUTH_TOKEN, } client_manager = self._make_clientmanager( auth_args=token_auth, auth_plugin_name='admin_token', ) self.assertEqual( fakes.AUTH_URL, client_manager._cli_options.config['auth']['endpoint'], ) self.assertEqual( fakes.AUTH_TOKEN, client_manager.auth.get_token(None), ) self.assertIsInstance( client_manager.auth, token_endpoint.Token, ) # NOTE(dtroyer): This is intentionally not assertFalse() as the return # value from is_service_available() may be == None self.assertNotEqual( False, client_manager.is_service_available('network'), ) def test_client_manager_password(self): client_manager = self._make_clientmanager( auth_required=True, ) self.assertEqual( fakes.AUTH_URL, client_manager._cli_options.config['auth']['auth_url'], ) self.assertEqual( fakes.USERNAME, client_manager._cli_options.config['auth']['username'], ) self.assertEqual( fakes.PASSWORD, client_manager._cli_options.config['auth']['password'], ) self.assertIsInstance( client_manager.auth, generic_plugin.Password, ) self.assertTrue(client_manager.verify) self.assertIsNone(client_manager.cert) # These need to stick around until the old-style clients are gone self.assertEqual( AUTH_REF.pop('version'), client_manager.auth_ref.version, ) self.assertEqual( fakes.to_unicode_dict(AUTH_REF), client_manager.auth_ref._data['access'], ) self.assertEqual( dir(SERVICE_CATALOG), dir(client_manager.auth_ref.service_catalog), ) self.assertTrue(client_manager.is_service_available('network')) def test_client_manager_password_verify(self): client_manager = self._make_clientmanager( auth_required=True, ) self.assertTrue(client_manager.verify) self.assertIsNone(client_manager.cacert) self.assertTrue(client_manager.is_service_available('network')) def test_client_manager_password_verify_ca(self): config_args = { 'cacert': 'cafile', } client_manager = self._make_clientmanager( config_args=config_args, auth_required=True, ) # Test that client_manager.verify is Requests-compatible, # i.e. it contains the value of cafile here self.assertTrue(client_manager.verify) self.assertEqual('cafile', client_manager.verify) self.assertEqual('cafile', client_manager.cacert) self.assertTrue(client_manager.is_service_available('network')) def test_client_manager_password_verify_false(self): config_args = { 'verify': False, } client_manager = self._make_clientmanager( config_args=config_args, auth_required=True, ) self.assertFalse(client_manager.verify) self.assertIsNone(client_manager.cacert) self.assertTrue(client_manager.is_service_available('network')) def test_client_manager_password_verify_insecure(self): config_args = { 'insecure': True, } client_manager = self._make_clientmanager( config_args=config_args, auth_required=True, ) self.assertFalse(client_manager.verify) self.assertIsNone(client_manager.cacert) self.assertTrue(client_manager.is_service_available('network')) def test_client_manager_password_verify_insecure_ca(self): config_args = { 'insecure': True, 'cacert': 'cafile', } client_manager = self._make_clientmanager( config_args=config_args, auth_required=True, ) # insecure overrides cacert self.assertFalse(client_manager.verify) self.assertIsNone(client_manager.cacert) self.assertTrue(client_manager.is_service_available('network')) def test_client_manager_password_client_cert(self): config_args = { 'cert': 'cert', } client_manager = self._make_clientmanager( config_args=config_args, ) self.assertEqual('cert', client_manager.cert) def test_client_manager_password_client_key(self): config_args = { 'cert': 'cert', 'key': 'key', } client_manager = self._make_clientmanager( config_args=config_args, ) self.assertEqual(('cert', 'key'), client_manager.cert) def test_client_manager_select_auth_plugin_password(self): # test password auth auth_args = { 'auth_url': fakes.AUTH_URL, 'username': fakes.USERNAME, 'password': fakes.PASSWORD, 'tenant_name': fakes.PROJECT_NAME, } self._make_clientmanager( auth_args=auth_args, identity_api_version='2.0', auth_plugin_name='v2password', ) auth_args = copy.deepcopy(self.default_password_auth) auth_args.update({ 'user_domain_name': 'default', 'project_domain_name': 'default', }) self._make_clientmanager( auth_args=auth_args, identity_api_version='3', auth_plugin_name='v3password', ) # Use v2.0 auth args auth_args = { 'auth_url': fakes.AUTH_URL, 'username': fakes.USERNAME, 'password': fakes.PASSWORD, 'tenant_name': fakes.PROJECT_NAME, } self._make_clientmanager( auth_args=auth_args, identity_api_version='2.0', ) # Use v3 auth args auth_args = copy.deepcopy(self.default_password_auth) auth_args.update({ 'user_domain_name': 'default', 'project_domain_name': 'default', }) self._make_clientmanager( auth_args=auth_args, identity_api_version='3', ) def test_client_manager_select_auth_plugin_token(self): # test token auth self._make_clientmanager( # auth_args=auth_args, identity_api_version='2.0', auth_plugin_name='v2token', ) self._make_clientmanager( # auth_args=auth_args, identity_api_version='3', auth_plugin_name='v3token', ) self._make_clientmanager( # auth_args=auth_args, identity_api_version='x', auth_plugin_name='token', ) def test_client_manager_select_auth_plugin_failure(self): self.assertRaises( ksa_exceptions.NoMatchingPlugin, self._make_clientmanager, identity_api_version='3', auth_plugin_name='bad_plugin', ) @mock.patch('osc_lib.api.auth.check_valid_authentication_options') def test_client_manager_auth_setup_once(self, check_authn_options_func): loader = loading.get_plugin_loader('password') auth_plugin = loader.load_from_options(**AUTH_DICT) client_manager = self._clientmanager_class()( cli_options=cloud_config.CloudConfig( name='t1', region='1', config=dict( auth_type='password', auth=AUTH_DICT, interface=fakes.INTERFACE, region_name=fakes.REGION_NAME, ), auth_plugin=auth_plugin, ), api_version={ 'identity': '2.0', }, ) self.assertFalse(client_manager._auth_setup_completed) client_manager.setup_auth() self.assertTrue(check_authn_options_func.called) self.assertTrue(client_manager._auth_setup_completed) # now make sure we don't do auth setup the second time around # by checking whether check_valid_auth_options() gets called again check_authn_options_func.reset_mock() client_manager.auth_ref check_authn_options_func.assert_not_called() def test_client_manager_endpoint_disabled(self): auth_args = copy.deepcopy(self.default_password_auth) auth_args.update({ 'user_domain_name': 'default', 'project_domain_name': 'default', }) # v3 fake doesn't have network endpoint client_manager = self._make_clientmanager( auth_args=auth_args, identity_api_version='3', auth_plugin_name='v3password', ) self.assertFalse(client_manager.is_service_available('network')) def test_client_manager_k2k_auth_setup(self): loader = loading.get_plugin_loader('password') auth_plugin = loader.load_from_options(**AUTH_DICT) client_manager = self._clientmanager_class()( cli_options=cloud_config.CloudConfig( name='t1', region='1', config=dict( auth_type='password', auth=AUTH_DICT, interface=fakes.INTERFACE, region_name=fakes.REGION_NAME, service_provider=fakes.SERVICE_PROVIDER_ID, remote_project_id=fakes.PROJECT_ID ), auth_plugin=auth_plugin, ), api_version={ 'identity': '3', }, ) self.assertFalse(client_manager._auth_setup_completed) client_manager.setup_auth() # Note(knikolla): Make sure that the auth object is of the correct # type and that the service_provider is correctly set. self.assertIsInstance(client_manager.auth, k2k.Keystone2Keystone) self.assertEqual(client_manager.auth._sp_id, fakes.SERVICE_PROVIDER_ID) self.assertEqual(client_manager.auth.project_id, fakes.PROJECT_ID) self.assertTrue(client_manager._auth_setup_completed) class TestClientManagerSDK(utils.TestClientManager): def test_client_manager_connection(self): client_manager = self._make_clientmanager( auth_required=True, ) if _occ_in_sdk: self.assertIsInstance( client_manager.sdk_connection, connection.Connection, ) else: self.assertIsNone(getattr(client_manager, 'sdk_connection', None)) osc-lib-1.9.0/osc_lib/__init__.py0000666000175100017510000000124113227377263016634 0ustar zuulzuul00000000000000# 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. # from osc_lib import version as _version __all__ = ['__version__'] __version__ = _version.version_string osc-lib-1.9.0/osc_lib/command/0000775000175100017510000000000013227377613016140 5ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/command/commandmanager.py0000666000175100017510000000377213227377263021477 0ustar zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # # 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.CommandManager""" import pkg_resources import cliff.commandmanager class CommandManager(cliff.commandmanager.CommandManager): """Add additional functionality to cliff.CommandManager Load additional command groups after initialization Add _command_group() methods """ def __init__(self, namespace, convert_underscores=True): self.group_list = [] super(CommandManager, self).__init__(namespace, convert_underscores) def load_commands(self, namespace): self.group_list.append(namespace) return super(CommandManager, self).load_commands(namespace) def add_command_group(self, group=None): """Adds another group of command entrypoints""" if group: self.load_commands(group) def get_command_groups(self): """Returns a list of the loaded command groups""" return self.group_list def get_command_names(self, group=None): """Returns a list of commands loaded for the specified group""" group_list = [] if group is not None: for ep in pkg_resources.iter_entry_points(group): cmd_name = ( ep.name.replace('_', ' ') if self.convert_underscores else ep.name ) group_list.append(cmd_name) return group_list return list(self.commands.keys()) osc-lib-1.9.0/osc_lib/command/timing.py0000666000175100017510000000245113227377263020006 0ustar zuulzuul00000000000000# 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. # """Timing Implementation""" from osc_lib.command import command class Timing(command.Lister): """Show timing data""" def take_action(self, parsed_args): column_headers = ( 'URL', 'Seconds', ) results = [] total = 0.0 for url, td in self.app.timing_data: # NOTE(dtroyer): Take the long way here because total_seconds() # was added in py27. sec = (td.microseconds + (td.seconds + td.days * 86400) * 1e6) / 1e6 total += sec results.append((url, sec)) results.append(('Total', total)) return ( column_headers, results, ) osc-lib-1.9.0/osc_lib/command/command.py0000666000175100017510000000367713227377263020150 0ustar zuulzuul00000000000000# Copyright 2016 NEC Corporation # # 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 abc import logging from cliff import command from cliff import lister from cliff import show import six from osc_lib import exceptions from osc_lib.i18n import _ class CommandMeta(abc.ABCMeta): def __new__(mcs, name, bases, cls_dict): if 'log' not in cls_dict: cls_dict['log'] = logging.getLogger( cls_dict['__module__'] + '.' + name) return super(CommandMeta, mcs).__new__(mcs, name, bases, cls_dict) @six.add_metaclass(CommandMeta) class Command(command.Command): def run(self, parsed_args): self.log.debug('run(%s)', parsed_args) return super(Command, self).run(parsed_args) def validate_os_beta_command_enabled(self): if not self.app.options.os_beta_command: msg = _('Caution: This is a beta command and subject to ' 'change. Use global option --os-beta-command ' 'to enable this command.') raise exceptions.CommandError(msg) def deprecated_option_warning(self, old_option, new_option): """Emit a warning for use of a deprecated option""" self.log.warning( _("The %(old)s option is deprecated, please use %(new)s instead.") % {'old': old_option, 'new': new_option} ) class Lister(Command, lister.Lister): pass class ShowOne(Command, show.ShowOne): pass osc-lib-1.9.0/osc_lib/command/__init__.py0000666000175100017510000000000013227377263020242 0ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/api/0000775000175100017510000000000013227377613015273 5ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/api/utils.py0000666000175100017510000000607113227377263017014 0ustar zuulzuul00000000000000# 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. # """API Utilities Library""" def simple_filter( data=None, attr=None, value=None, property_field=None, ): """Filter a list of dicts :param list data: The list to be filtered. The list is modified in-place and will be changed if any filtering occurs. :param string attr: The name of the attribute to filter. If attr does not exist no match will succeed and no rows will be returned. If attr is None no filtering will be performed and all rows will be returned. :param string value: The value to filter. None is considered to be a 'no filter' value. '' matches against a Python empty string. :param string property_field: The name of the data field containing a property dict to filter. If property_field is None, attr is a field name. If property_field is not None, attr is a property key name inside the named property field. :returns: Returns the filtered list :rtype list: This simple filter (one attribute, one exact-match value) searches a list of dicts to select items. It first searches the item dict for a matching ``attr`` then does an exact-match on the ``value``. If ``property_field`` is given, it will look inside that field (if it exists and is a dict) for a matching ``value``. """ # Take the do-nothing case shortcut if not data or not attr or value is None: return data # NOTE:(dtroyer): This filter modifies the provided list in-place using # list.remove() so we need to start at the end so the loop pointer does # not skip any items after a deletion. for d in reversed(data): if attr in d: # Searching data fields search_value = d[attr] elif (property_field and property_field in d and isinstance(d[property_field], dict)): # Searching a properties field - do this separately because # we don't want to fail over to checking the fields if a # property name is given. if attr in d[property_field]: search_value = d[property_field][attr] else: search_value = None else: search_value = None # could do regex here someday... if not search_value or search_value != value: # remove from list try: data.remove(d) except ValueError: # it's already gone! pass return data osc-lib-1.9.0/osc_lib/api/api.py0000666000175100017510000003123513227377263016425 0ustar zuulzuul00000000000000# 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. # """Base API Library""" import simplejson as json import six from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1 import session as ksa_session from osc_lib import exceptions from osc_lib.i18n import _ class BaseAPI(object): """Base API wrapper for keystoneauth1.session.Session Encapsulate the translation between keystoneauth1.session.Session and requests.Session in a single layer: * Restore some requests.session.Session compatibility; keystoneauth1.session.Session.request() has the method and url arguments swapped from the rest of the requests-using world. * Provide basic endpoint handling when a Service Catalog is not available. """ # Which service are we? Set in API-specific subclasses SERVICE_TYPE = "" # The common OpenStack microversion header HEADER_NAME = "OpenStack-API-Version" def __init__( self, session=None, service_type=None, endpoint=None, **kwargs ): """Base object that contains some common API objects and methods :param keystoneauth1.session.Session session: The session to be used for making the HTTP API calls. If None, a default keystoneauth1.session.Session will be created. :param string service_type: API name, i.e. ``identity`` or ``compute`` :param string endpoint: An optional URL to be used as the base for API requests on this API. :param kwargs: Keyword arguments passed to keystoneauth1.session.Session(). """ super(BaseAPI, self).__init__() # Create a keystoneauth1.session.Session if one is not supplied if not session: self.session = ksa_session.Session(**kwargs) else: self.session = session self.service_type = service_type self.endpoint = self._munge_endpoint(endpoint) def _munge_endpoint(self, endpoint): """Hook to allow subclasses to massage the passed-in endpoint Hook to massage passed-in endpoints from arbitrary sources, including direct user input. By default just remove trailing '/' as all of our path info strings start with '/' and not all services can handle '//' in their URLs. Some subclasses will override this to do additional work, most likely with regard to API versions. :param string endpoint: The service endpoint, generally direct from the service catalog. :return: The modified endpoint """ if isinstance(endpoint, six.string_types): return endpoint.rstrip('/') else: return endpoint def _request(self, method, url, session=None, **kwargs): """Perform call into session All API calls are funneled through this method to provide a common place to finalize the passed URL and other things. :param string method: The HTTP method name, i.e. ``GET``, ``PUT``, etc :param string url: The API-specific portion of the URL path, or a full URL if ``endpoint`` was not supplied at initialization. :param keystoneauth1.session.Session session: An initialized session to override the one created at initialization. :param kwargs: Keyword arguments passed to requests.request(). :return: the requests.Response object """ # If session arg is supplied, use it this time, but don't save it if not session: session = self.session # Do the auto-endpoint magic if self.endpoint: if url: url = '/'.join([self.endpoint.rstrip('/'), url.lstrip('/')]) else: # NOTE(dtroyer): This is left here after _munge_endpoint() is # added because endpoint is public and there is # no accounting for what may happen. url = self.endpoint.rstrip('/') else: # Pass on the lack of URL unmolested to maintain the same error # handling from keystoneauth: raise EndpointNotFound pass # Hack out empty headers 'cause KSA can't stomach it if 'headers' in kwargs and kwargs['headers'] is None: kwargs.pop('headers') # Why is ksc session backwards??? return session.request(url, method, **kwargs) # The basic action methods all take a Session and return dict/lists def create( self, url, session=None, method=None, **params ): """Create a new resource :param string url: The API-specific portion of the URL path :param Session session: HTTP client session :param string method: HTTP method (default POST) """ if not method: method = 'POST' ret = self._request(method, url, session=session, **params) # Should this move into _requests()? try: return ret.json() except json.JSONDecodeError: return ret def delete( self, url, session=None, **params ): """Delete a resource :param string url: The API-specific portion of the URL path :param Session session: HTTP client session """ return self._request('DELETE', url, **params) def list( self, path, session=None, body=None, detailed=False, headers=None, **params ): """Return a list of resources GET ${ENDPOINT}/${PATH}?${PARAMS} path is often the object's plural resource type :param string path: The API-specific portion of the URL path :param Session session: HTTP client session :param body: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param bool detailed: Adds '/details' to path for some APIs to return extended attributes :param dict headers: Headers dictionary to pass to requests :returns: JSON-decoded response, could be a list or a dict-wrapped-list """ if detailed: path = '/'.join([path.rstrip('/'), 'details']) if body: ret = self._request( 'POST', path, # service=self.service_type, json=body, params=params, headers=headers, ) else: ret = self._request( 'GET', path, # service=self.service_type, params=params, headers=headers, ) try: return ret.json() except json.JSONDecodeError: return ret # Layered actions built on top of the basic action methods do not # explicitly take a Session but one may still be passed in kwargs def find_attr( self, path, value=None, attr=None, resource=None, ): """Find a resource via attribute or ID Most APIs return a list wrapped by a dict with the resource name as key. Some APIs (Identity) return a dict when a query string is present and there is one return value. Take steps to unwrap these bodies and return a single dict without any resource wrappers. :param string path: The API-specific portion of the URL path :param string value: value to search for :param string attr: attribute to use for resource search :param string resource: plural of the object resource name; defaults to path For example: n = find(netclient, 'network', 'networks', 'matrix') """ # Default attr is 'name' if attr is None: attr = 'name' # Default resource is path - in many APIs they are the same if resource is None: resource = path def getlist(kw): """Do list call, unwrap resource dict if present""" ret = self.list(path, **kw) if isinstance(ret, dict) and resource in ret: ret = ret[resource] return ret # Search by attribute kwargs = {attr: value} data = getlist(kwargs) if isinstance(data, dict): return data if len(data) == 1: return data[0] if len(data) > 1: msg = _("Multiple %(resource)s exist with %(attr)s='%(value)s'") raise exceptions.CommandError( msg % {'resource': resource, 'attr': attr, 'value': value} ) # Search by id kwargs = {'id': value} data = getlist(kwargs) if len(data) == 1: return data[0] msg = _("No %(resource)s with a %(attr)s or ID of '%(value)s' found") raise exceptions.CommandError( msg % {'resource': resource, 'attr': attr, 'value': value} ) def find_bulk( self, path, headers=None, **kwargs ): """Bulk load and filter locally :param string path: The API-specific portion of the URL path :param kwargs: A dict of AVPs to match - logical AND :param dict headers: Headers dictionary to pass to requests :returns: list of resource dicts """ print("keys: %s" % kwargs.keys()) items = self.list(path) if isinstance(items, dict): # strip off the enclosing dict key = list(items.keys())[0] items = items[key] ret = [] for o in items: try: if all(o[attr] == kwargs[attr] for attr in kwargs.keys()): ret.append(o) except KeyError: continue return ret def find_one( self, path, **kwargs ): """Find a resource by name or ID :param string path: The API-specific portion of the URL path :returns: resource dict """ bulk_list = self.find_bulk(path, **kwargs) num_bulk = len(bulk_list) if num_bulk == 0: msg = _("none found") raise exceptions.NotFound(msg) elif num_bulk > 1: msg = _("many found") raise RuntimeError(msg) return bulk_list[0] def find( self, path, value=None, attr=None, headers=None, ): """Find a single resource by name or ID :param string path: The API-specific portion of the URL path :param string value: search expression (required, really) :param string attr: name of attribute for secondary search :param dict headers: Headers dictionary to pass to requests """ def raise_not_found(): msg = _("%s not found") % value raise exceptions.NotFound(msg) try: ret = self._request( 'GET', "/%s/%s" % (path, value), headers=headers, ).json() if isinstance(ret, dict): # strip off the enclosing dict key = list(ret.keys())[0] ret = ret[key] except ( ksa_exceptions.NotFound, ksa_exceptions.BadRequest, ): if attr: kwargs = {attr: value} try: ret = self.find_one( path, headers=headers, **kwargs ) except ( exceptions.NotFound, ksa_exceptions.NotFound, ): raise_not_found() else: raise_not_found() return ret osc-lib-1.9.0/osc_lib/api/__init__.py0000666000175100017510000000000013227377263017375 0ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/api/auth.py0000666000175100017510000001752213227377263016620 0ustar zuulzuul00000000000000# 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. # """Authentication Library""" import argparse from keystoneauth1.identity.v3 import k2k from keystoneauth1.loading import base from osc_lib import exceptions as exc from osc_lib.i18n import _ from osc_lib import utils # Initialize the list of Authentication plugins early in order # to get the command-line options PLUGIN_LIST = None # List of plugin command line options OPTIONS_LIST = {} def get_plugin_list(): """Gather plugin list and cache it""" global PLUGIN_LIST if PLUGIN_LIST is None: PLUGIN_LIST = base.get_available_plugin_names() return PLUGIN_LIST def get_options_list(): """Gather plugin options so the help action has them available""" global OPTIONS_LIST if not OPTIONS_LIST: for plugin_name in get_plugin_list(): plugin_options = base.get_plugin_options(plugin_name) for o in plugin_options: os_name = o.name.lower().replace('_', '-') os_env_name = 'OS_' + os_name.upper().replace('-', '_') OPTIONS_LIST.setdefault( os_name, {'env': os_env_name, 'help': ''}, ) # TODO(mhu) simplistic approach, would be better to only add # help texts if they vary from one auth plugin to another # also the text rendering is ugly in the CLI ... OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % ( plugin_name, o.help, ) return OPTIONS_LIST def check_valid_authorization_options(options, auth_plugin_name): """Validate authorization options, and provide helpful error messages.""" if (options.auth.get('project_id') and not options.auth.get('domain_id') and not options.auth.get('domain_name') and not options.auth.get('project_name') and not options.auth.get('tenant_id') and not options.auth.get('tenant_name')): raise exc.CommandError(_( 'Missing parameter(s): ' 'Set either a project or a domain scope, but not both. Set a ' 'project scope with --os-project-name, OS_PROJECT_NAME, or ' 'auth.project_name. Alternatively, set a domain scope with ' '--os-domain-name, OS_DOMAIN_NAME or auth.domain_name.' )) def check_valid_authentication_options(options, auth_plugin_name): """Validate authentication options, and provide helpful error messages :param required_scope: indicate whether a scoped token is required """ # Get all the options defined within the plugin. plugin_opts = base.get_plugin_options(auth_plugin_name) plugin_opts = {opt.dest: opt for opt in plugin_opts} # NOTE(aloga): this is an horrible hack. We need a way to specify the # required options in the plugins. Using the "required" argument for # the oslo_config.cfg.Opt does not work, as it is not possible to load the # plugin if the option is not defined, so the error will simply be: # "NoMatchingPlugin: The plugin foobar could not be found" msgs = [] # when no auth params are passed in, user advised to use os-cloud if not options.auth: msgs.append(_( 'Set a cloud-name with --os-cloud or OS_CLOUD' )) else: if 'password' in plugin_opts and not options.auth.get('username'): msgs.append(_( 'Set a username with --os-username, OS_USERNAME,' ' or auth.username' )) if 'auth_url' in plugin_opts and not options.auth.get('auth_url'): msgs.append(_( 'Set an authentication URL, with --os-auth-url,' ' OS_AUTH_URL or auth.auth_url' )) if 'url' in plugin_opts and not options.auth.get('url'): msgs.append(_( 'Set a service URL, with --os-url, OS_URL or auth.url' )) if 'token' in plugin_opts and not options.auth.get('token'): msgs.append(_( 'Set a token with --os-token, OS_TOKEN or auth.token' )) if msgs: raise exc.CommandError( _('Missing parameter(s): \n%s') % '\n'.join(msgs) ) def build_auth_plugins_option_parser(parser): """Auth plugins options builder Builds dynamically the list of options expected by each available authentication plugin. """ available_plugins = list(get_plugin_list()) parser.add_argument( '--os-auth-type', metavar='', dest='auth_type', default=utils.env('OS_AUTH_TYPE'), help=_('Select an authentication type. Available types: %s.' ' Default: selected based on --os-username/--os-token' ' (Env: OS_AUTH_TYPE)') % ', '.join(available_plugins), choices=available_plugins ) # Maintain compatibility with old tenant env vars envs = { 'OS_PROJECT_NAME': utils.env( 'OS_PROJECT_NAME', default=utils.env('OS_TENANT_NAME') ), 'OS_PROJECT_ID': utils.env( 'OS_PROJECT_ID', default=utils.env('OS_TENANT_ID') ), } for o in get_options_list(): # Remove tenant options from KSC plugins and replace them below if 'tenant' not in o: parser.add_argument( '--os-' + o, metavar='' % o, dest=o.replace('-', '_'), default=envs.get( OPTIONS_LIST[o]['env'], utils.env(OPTIONS_LIST[o]['env']), ), help=_('%(help)s\n(Env: %(env)s)') % { 'help': OPTIONS_LIST[o]['help'], 'env': OPTIONS_LIST[o]['env'], }, ) # add tenant-related options for compatibility # this is deprecated but still used in some tempest tests... parser.add_argument( '--os-tenant-name', metavar='', dest='os_project_name', help=argparse.SUPPRESS, ) parser.add_argument( '--os-tenant-id', metavar='', dest='os_project_id', help=argparse.SUPPRESS, ) return parser def get_keystone2keystone_auth(local_auth, service_provider, project_id=None, project_name=None, project_domain_id=None, project_domain_name=None): """Return Keystone 2 Keystone authentication for service provider. :param local_auth: authentication to use with the local Keystone :param service_provider: service provider id as registered in Keystone :param project_id: project id to scope to in the service provider :param project_name: project name to scope to in the service provider :param project_domain_id: id of domain in the service provider :param project_domain_name: name of domain to in the service provider :return: Keystone2Keystone auth object for service provider """ return k2k.Keystone2Keystone(local_auth, service_provider, project_id=project_id, project_name=project_name, project_domain_id=project_domain_id, project_domain_name=project_domain_name) osc-lib-1.9.0/osc_lib/exceptions.py0000666000175100017510000000631113227377263017261 0ustar zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # # 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. # """Exception definitions.""" class CommandError(Exception): pass class AuthorizationFailure(Exception): pass class PluginAttributeError(Exception): """A plugin threw an AttributeError while being lazily loaded.""" # This *must not* inherit from AttributeError; # that would defeat the whole purpose. pass class NoTokenLookupException(Exception): """This does not support looking up endpoints from an existing token.""" pass class EndpointNotFound(Exception): """Could not find Service or Region in Service Catalog.""" pass class UnsupportedVersion(Exception): """The user is trying to use an unsupported version of the API""" pass class InvalidValue(Exception): """An argument value is not valid: wrong type, out of range, etc""" message = "Supplied value is not valid" class ClientException(Exception): """The base exception class for all exceptions this library raises.""" def __init__(self, code, message=None, details=None): self.code = code self.message = message or self.__class__.message self.details = details def __str__(self): return "%s (HTTP %s)" % (self.message, self.code) class BadRequest(ClientException): """HTTP 400 - Bad request: you sent some malformed data.""" http_status = 400 message = "Bad request" class Unauthorized(ClientException): """HTTP 401 - Unauthorized: bad credentials.""" http_status = 401 message = "Unauthorized" class Forbidden(ClientException): """HTTP 403 - Forbidden: not authorized to access to this resource.""" http_status = 403 message = "Forbidden" class NotFound(ClientException): """HTTP 404 - Not found""" http_status = 404 message = "Not found" class Conflict(ClientException): """HTTP 409 - Conflict""" http_status = 409 message = "Conflict" class OverLimit(ClientException): """HTTP 413 - Over limit: reached the API limits for this time period.""" http_status = 413 message = "Over limit" # NotImplemented is a python keyword. class HTTPNotImplemented(ClientException): """HTTP 501 - Not Implemented: server does not support this operation.""" http_status = 501 message = "Not Implemented" # In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__() # so we can do this: # _code_map = dict((c.http_status, c) # for c in ClientException.__subclasses__()) # # Instead, we have to hardcode it: _code_map = dict((c.http_status, c) for c in [ BadRequest, Unauthorized, Forbidden, NotFound, OverLimit, HTTPNotImplemented ]) osc-lib-1.9.0/osc_lib/version.py0000666000175100017510000000130613227377263016564 0ustar zuulzuul00000000000000# 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 pbr.version __all__ = ['version_info', 'version_string'] version_info = pbr.version.VersionInfo('osc-lib') version_string = version_info.version_string() osc-lib-1.9.0/osc_lib/utils/0000775000175100017510000000000013227377613015662 5ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib/utils/__init__.py0000666000175100017510000005751213227377263020010 0ustar zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # # 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. # """Common client utilities""" import copy import getpass import logging import os import six import time import warnings from cliff import columns as cliff_columns from oslo_utils import importutils from osc_lib import exceptions from osc_lib.i18n import _ LOG = logging.getLogger(__name__) def backward_compat_col_lister(column_headers, columns, column_map): """Convert the column headers to keep column backward compatibility. Replace the new column name of column headers by old name, so that the column headers can continue to support to show the old column name by --column/-c option with old name, like: volume list -c 'Display Name' :param column_headers: The column headers to be output in list command. :param columns: The columns to be output. :param column_map: The key of map is old column name, the value is new column name, like: {'old_col': 'new_col'} """ if not columns: return column_headers # NOTE(RuiChen): column_headers may be a tuple in some code, like: # volume v1, convert it to a list in order to change # the column name. column_headers = list(column_headers) for old_col, new_col in six.iteritems(column_map): if old_col in columns: LOG.warning(_('The column "%(old_column)s" was deprecated, ' 'please use "%(new_column)s" replace.') % { 'old_column': old_col, 'new_column': new_col} ) if new_col in column_headers: column_headers[column_headers.index(new_col)] = old_col return column_headers def backward_compat_col_showone(show_object, columns, column_map): """Convert the output object to keep column backward compatibility. Replace the new column name of output object by old name, so that the object can continue to support to show the old column name by --column/-c option with old name, like: volume show -c 'display_name' :param show_object: The object to be output in create/show commands. :param columns: The columns to be output. :param column_map: The key of map is old column name, the value is new column name, like: {'old_col': 'new_col'} """ if not columns: return show_object show_object = copy.deepcopy(show_object) for old_col, new_col in six.iteritems(column_map): if old_col in columns: LOG.warning(_('The column "%(old_column)s" was deprecated, ' 'please use "%(new_column)s" replace.') % { 'old_column': old_col, 'new_column': new_col} ) if new_col in show_object: show_object.update({old_col: show_object.pop(new_col)}) return show_object def build_kwargs_dict(arg_name, value): """Return a dictionary containing `arg_name` if `value` is set.""" kwargs = {} if value: kwargs[arg_name] = value return kwargs def calculate_header_and_attrs(column_headers, attrs, parsed_args): """Calculate headers and attribute names based on parsed_args.column. When --column (-c) option is specified, this function calculates column headers and expected API attribute names according to the OSC header/column definitions. This function also adjusts the content of parsed_args.columns if API attribute names are used in parsed_args.columns. This allows users to specify API attribute names in -c option. :param column_headers: A tuple/list of column headers to display :param attrs: a tuple/list of API attribute names. The order of corresponding column header and API attribute name must match. :param parsed_args: Parsed argument object returned by argparse parse_args :returns: A tuple of calculated headers and API attribute names. """ if parsed_args.columns: header_attr_map = dict(zip(column_headers, attrs)) expected_attrs = [header_attr_map.get(c, c) for c in parsed_args.columns] attr_header_map = dict(zip(attrs, column_headers)) expected_headers = [attr_header_map.get(c, c) for c in parsed_args.columns] # If attribute name is used in parsed_args.columns # convert it into display names because cliff expects # name in parsed_args.columns and name in column_headers matches. parsed_args.columns = expected_headers return expected_headers, expected_attrs else: return column_headers, attrs def env(*vars, **kwargs): """Search for the first defined of possibly many env vars Returns the first environment variable defined in vars, or returns the default defined in kwargs. """ for v in vars: value = os.environ.get(v, None) if value: return value return kwargs.get('default', '') def find_min_match(items, sort_attr, **kwargs): """Find all resources meeting the given minimum constraints :param items: A List of objects to consider :param sort_attr: Attribute to sort the resulting list :param kwargs: A dict of attributes and their minimum values :rtype: A list of resources osrted by sort_attr that meet the minimums """ def minimum_pieces_of_flair(item): """Find lowest value greater than the minumum""" result = True for k in kwargs: # AND together all of the given attribute results result = result and kwargs[k] <= get_field(item, k) return result return sort_items(filter(minimum_pieces_of_flair, items), sort_attr) def find_resource(manager, name_or_id, **kwargs): """Helper for the _find_* methods. :param manager: A client manager class :param name_or_id: The resource we are trying to find :param kwargs: To be used in calling .find() :rtype: The found resource This method will attempt to find a resource in a variety of ways. Primarily .get() methods will be called with `name_or_id` as an integer value, and tried again as a string value. If both fail, then a .find() is attempted, which is essentially calling a .list() function with a 'name' query parameter that is set to `name_or_id`. Lastly, if any kwargs are passed in, they will be treated as additional query parameters. This is particularly handy in the case of finding resources in a domain. """ # Case 1: name_or_id is an ID, we need to call get() directly # for example: /projects/454ad1c743e24edcad846d1118837cac # For some projects, the name only will work. For keystone, this is not # enough information, and domain information is necessary. try: return manager.get(name_or_id) except Exception: pass if kwargs: # Case 2: name_or_id is a name, but we have query args in kwargs # for example: /projects/demo&domain_id=30524568d64447fbb3fa8b7891c10dd try: return manager.get(name_or_id, **kwargs) except Exception: pass # Case 3: Try to get entity as integer id. Keystone does not have integer # IDs, they are UUIDs, but some things in nova do, like flavors. try: if isinstance(name_or_id, int) or name_or_id.isdigit(): return manager.get(int(name_or_id), **kwargs) # FIXME(dtroyer): The exception to catch here is dependent on which # client library the manager passed in belongs to. # Eventually this should be pulled from a common set # of client exceptions. except Exception as ex: if (type(ex).__name__ == 'NotFound' or type(ex).__name__ == 'HTTPNotFound' or type(ex).__name__ == 'TypeError'): pass else: raise # Case 4: Try to use find. # Reset the kwargs here for find if len(kwargs) == 0: kwargs = {} try: # Prepare the kwargs for calling find if 'NAME_ATTR' in manager.resource_class.__dict__: # novaclient does this for oddball resources kwargs[manager.resource_class.NAME_ATTR] = name_or_id else: kwargs['name'] = name_or_id except Exception: pass # finally try to find entity by name try: return manager.find(**kwargs) # FIXME(dtroyer): The exception to catch here is dependent on which # client library the manager passed in belongs to. # Eventually this should be pulled from a common set # of client exceptions. except Exception as ex: if type(ex).__name__ == 'NotFound': msg = _( "No %(resource)s with a name or ID of '%(id)s' exists." ) raise exceptions.CommandError(msg % { 'resource': manager.resource_class.__name__.lower(), 'id': name_or_id, }) if type(ex).__name__ == 'NoUniqueMatch': msg = _( "More than one %(resource)s exists with the name '%(id)s'." ) raise exceptions.CommandError(msg % { 'resource': manager.resource_class.__name__.lower(), 'id': name_or_id, }) else: pass # Case 5: For client with no find function, list all resources and hope # to find a matching name or ID. count = 0 for resource in manager.list(): if (resource.get('id') == name_or_id or resource.get('name') == name_or_id): count += 1 _resource = resource if count == 0: # we found no match, report back this error: msg = _("Could not find resource %s") raise exceptions.CommandError(msg % name_or_id) elif count == 1: return _resource else: # we found multiple matches, report back this error msg = _("More than one resource exists with the name or ID '%s'.") raise exceptions.CommandError(msg % name_or_id) def format_dict(data): """Return a formatted string of key value pairs :param data: a dict :rtype: a string formatted to key='value' """ if data is None: return None output = "" for s in sorted(data): output = output + s + "='" + six.text_type(data[s]) + "', " return output[:-2] def format_dict_of_list(data, separator='; '): """Return a formatted string of key value pair :param data: a dict, key is string, value is a list of string, for example: {u'public': [u'2001:db8::8', u'172.24.4.6']} :param separator: the separator to use between key/value pair (default: '; ') :return: a string formatted to {'key1'=['value1', 'value2']} with separated by separator """ if data is None: return None output = [] for key in sorted(data): value = data[key] if value is None: continue value_str = format_list(value) group = "%s=%s" % (key, value_str) output.append(group) return separator.join(output) def format_list(data, separator=', '): """Return a formatted strings :param data: a list of strings :param separator: the separator to use between strings (default: ', ') :rtype: a string formatted based on separator """ if data is None: return None return separator.join(sorted(data)) def format_list_of_dicts(data): """Return a formatted string of key value pairs for each dict :param data: a list of dicts :rtype: a string formatted to key='value' with dicts separated by new line """ if data is None: return None return '\n'.join(format_dict(i) for i in data) def format_size(size): """Display size of a resource in a human readable format :param string size: The size of the resource in bytes. :returns: Returns the size in human-friendly format :rtype string: This function converts the size (provided in bytes) of a resource into a human-friendly format such as K, M, G, T, P, E, Z """ suffix = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z'] base = 1000.0 index = 0 if size is None: size = 0 while size >= base: index = index + 1 size = size / base padded = '%.1f' % size stripped = padded.rstrip('0').rstrip('.') return '%s%s' % (stripped, suffix[index]) def get_client_class(api_name, version, version_map): """Returns the client class for the requested API version :param api_name: the name of the API, e.g. 'compute', 'image', etc :param version: the requested API version :param version_map: a dict of client classes keyed by version :rtype: a client class for the requested API version """ try: client_path = version_map[str(version)] except (KeyError, ValueError): sorted_versions = sorted(version_map.keys(), key=lambda s: list(map(int, s.split('.')))) msg = _( "Invalid %(api_name)s client version '%(version)s'. " "must be one of: %(version_map)s" ) raise exceptions.UnsupportedVersion(msg % { 'api_name': api_name, 'version': version, 'version_map': ', '.join(sorted_versions), }) return importutils.import_class(client_path) def get_dict_properties(item, fields, mixed_case_fields=None, formatters=None): """Return a tuple containing the item properties. :param item: a single dict resource :param fields: tuple of strings with the desired field names :param mixed_case_fields: tuple of field names to preserve case :param formatters: dictionary mapping field names to callables to format the values """ if mixed_case_fields is None: mixed_case_fields = [] if formatters is None: formatters = {} row = [] for field in fields: if field in mixed_case_fields: field_name = field.replace(' ', '_') else: field_name = field.lower().replace(' ', '_') data = item[field_name] if field_name in item else '' if field in formatters: formatter = formatters[field] if issubclass(formatter, cliff_columns.FormattableColumn): data = formatter(data) else: warnings.warn( 'The usage of formatter functions is now discouraged. ' 'Consider using cliff.columns.FormattableColumn instead. ' 'See reviews linked with bug 1687955 for more detail.', category=DeprecationWarning) if data is not None: data = formatter(data) row.append(data) return tuple(row) def get_effective_log_level(): """Returns the lowest logging level considered by logging handlers Retrieve and return the smallest log level set among the root logger's handlers (in case of multiple handlers). """ root_log = logging.getLogger() min_log_lvl = logging.CRITICAL for handler in root_log.handlers: min_log_lvl = min(min_log_lvl, handler.level) return min_log_lvl def get_field(item, field): try: if isinstance(item, dict): return item[field] else: return getattr(item, field) except Exception: msg = _("Resource doesn't have field %s") raise exceptions.CommandError(msg % field) def get_item_properties(item, fields, mixed_case_fields=None, formatters=None): """Return a tuple containing the item properties. :param item: a single item resource (e.g. Server, Project, etc) :param fields: tuple of strings with the desired field names :param mixed_case_fields: tuple of field names to preserve case :param formatters: dictionary mapping field names to callables to format the values """ if mixed_case_fields is None: mixed_case_fields = [] if formatters is None: formatters = {} row = [] for field in fields: if field in mixed_case_fields: field_name = field.replace(' ', '_') else: field_name = field.lower().replace(' ', '_') data = getattr(item, field_name, '') if field in formatters: formatter = formatters[field] if issubclass(formatter, cliff_columns.FormattableColumn): data = formatter(data) else: warnings.warn( 'The usage of formatter functions is now discouraged. ' 'Consider using cliff.columns.FormattableColumn instead. ' 'See reviews linked with bug 1687955 for more detail.', category=DeprecationWarning) if data is not None: data = formatter(data) row.append(data) return tuple(row) def get_password(stdin, prompt=None, confirm=True): message = prompt or "User Password:" if hasattr(stdin, 'isatty') and stdin.isatty(): try: while True: first_pass = getpass.getpass(message) if not confirm: return first_pass second_pass = getpass.getpass("Repeat " + message) if first_pass == second_pass: return first_pass msg = _("The passwords entered were not the same") print(msg) except EOFError: # Ctl-D msg = _("Error reading password") raise exceptions.CommandError(msg) msg = _("No terminal detected attempting to read password") raise exceptions.CommandError(msg) def is_ascii(string): try: (string.decode('ascii') if isinstance(string, bytes) else string.encode('ascii')) return True except (UnicodeEncodeError, UnicodeDecodeError): return False def read_blob_file_contents(blob_file): try: with open(blob_file) as file: blob = file.read().strip() return blob except IOError: msg = _("Error occurred trying to read from file %s") raise exceptions.CommandError(msg % blob_file) def sort_items(items, sort_str, sort_type=None): """Sort items based on sort keys and sort directions given by sort_str. :param items: a list or generator object of items :param sort_str: a string defining the sort rules, the format is ':[direction1],:[direction2]...', direction can be 'asc' for ascending or 'desc' for descending, if direction is not given, it's ascending by default :return: sorted items """ if not sort_str: return items # items may be a generator object, transform it to a list items = list(items) sort_keys = sort_str.strip().split(',') for sort_key in reversed(sort_keys): reverse = False if ':' in sort_key: sort_key, direction = sort_key.split(':', 1) if not sort_key: msg = _("''' is not a valid sort key") raise exceptions.CommandError(msg) if direction not in ['asc', 'desc']: if not direction: direction = "" msg = _( "'%(direction)s' is not a valid sort direction for " "sort key %(sort_key)s, use 'asc' or 'desc' instead" ) raise exceptions.CommandError(msg % { 'direction': direction, 'sort_key': sort_key, }) if direction == 'desc': reverse = True def f(x): # Attempts to convert items to same 'sort_type' if provided. # This is due to Python 3 throwing TypeError if you attempt to # compare different types item = get_field(x, sort_key) if sort_type and not isinstance(item, sort_type): try: item = sort_type(item) except Exception: # Can't convert, so no sensible way to compare item = sort_type() return item items.sort(key=f, reverse=reverse) return items def wait_for_delete(manager, res_id, status_field='status', error_status=['error'], exception_name=['NotFound'], sleep_time=5, timeout=300, callback=None): """Wait for resource deletion :param manager: the manager from which we can get the resource :param res_id: the resource id to watch :param status_field: the status attribute in the returned resource object, this is used to check for error states while the resource is being deleted :param error_status: a list of status strings for error :param exception_name: a list of exception strings for deleted case :param sleep_time: wait this long between checks (seconds) :param timeout: check until this long (seconds) :param callback: called per sleep cycle, useful to display progress; this function is passed a progress value during each iteration of the wait loop :rtype: True on success, False if the resource has gone to error state or the timeout has been reached """ total_time = 0 while total_time < timeout: try: # might not be a bad idea to re-use find_resource here if it was # a bit more friendly in the exceptions it raised so we could just # handle a NotFound exception here without parsing the message res = manager.get(res_id) except Exception as ex: if type(ex).__name__ in exception_name: return True raise status = getattr(res, status_field, '').lower() if status in error_status: return False if callback: progress = getattr(res, 'progress', None) or 0 callback(progress) time.sleep(sleep_time) total_time += sleep_time # if we got this far we've timed out return False def wait_for_status(status_f, res_id, status_field='status', success_status=['active'], error_status=['error'], sleep_time=5, callback=None): """Wait for status change on a resource during a long-running operation :param status_f: a status function that takes a single id argument :param res_id: the resource id to watch :param status_field: the status attribute in the returned resource object :param success_status: a list of status strings for successful completion :param error_status: a list of status strings for error :param sleep_time: wait this long (seconds) :param callback: called per sleep cycle, useful to display progress :rtype: True on success """ while True: res = status_f(res_id) status = getattr(res, status_field, '').lower() if status in success_status: retval = True break elif status in error_status: retval = False break if callback: progress = getattr(res, 'progress', None) or 0 callback(progress) time.sleep(sleep_time) return retval osc-lib-1.9.0/osc_lib/utils/columns.py0000666000175100017510000000740413227377263017724 0ustar zuulzuul00000000000000# 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 operator LIST_BOTH = 'both' LIST_SHORT_ONLY = 'short_only' LIST_LONG_ONLY = 'long_only' def get_column_definitions(attr_map, long_listing): """Return table headers and column names for a listing table. An attribute map (attr_map) is a list of table entry definitions and the format of the map is as follows: :param attr_map: a list of table entry definitions. Each entry should be a tuple consisting of (API attribute name, header name, listing mode). For example: .. code-block:: python (('id', 'ID', LIST_BOTH), ('name', 'Name', LIST_BOTH), ('tenant_id', 'Project', LIST_LONG_ONLY)) The third field of each tuple must be one of LIST_BOTH, LIST_LONG_ONLY (a corresponding column is shown only in a long mode), or LIST_SHORT_ONLY (a corresponding column is shown only in a short mode). :param long_listing: A boolean value which indicates a long listing or not. In most cases, parsed_args.long is passed to this argument. :return: A tuple of a list of table headers and a list of column names. """ if long_listing: headers = [hdr for col, hdr, listing_mode in attr_map if listing_mode in (LIST_BOTH, LIST_LONG_ONLY)] columns = [col for col, hdr, listing_mode in attr_map if listing_mode in (LIST_BOTH, LIST_LONG_ONLY)] else: headers = [hdr for col, hdr, listing_mode in attr_map if listing_mode if listing_mode in (LIST_BOTH, LIST_SHORT_ONLY)] columns = [col for col, hdr, listing_mode in attr_map if listing_mode if listing_mode in (LIST_BOTH, LIST_SHORT_ONLY)] return headers, columns def get_columns(item, attr_map=None): """Return pair of resource attributes and corresponding display names. :param item: a dictionary which represents a resource. Keys of the dictionary are expected to be attributes of the resource. Values are not referred to by this method. .. code-block:: python {'id': 'myid', 'name': 'myname', 'foo': 'bar', 'tenant_id': 'mytenan'} :param attr_map: a list of mapping from attribute to display name. The same format is used as for get_column_definitions attr_map. .. code-block:: python (('id', 'ID', LIST_BOTH), ('name', 'Name', LIST_BOTH), ('tenant_id', 'Project', LIST_LONG_ONLY)) :return: A pair of tuple of attributes and tuple of display names. .. code-block:: python (('id', 'name', 'tenant_id', 'foo'), # attributes ('ID', 'Name', 'Project', 'foo') # display names Both tuples of attributes and display names are sorted by display names in the alphabetical order. Attributes not found in a given attr_map are kept as-is. """ attr_map = attr_map or tuple([]) _attr_map_dict = dict((col, hdr) for col, hdr, listing_mode in attr_map) columns = [(column, _attr_map_dict.get(column, column)) for column in item.keys()] columns = sorted(columns, key=operator.itemgetter(1)) return (tuple(col[0] for col in columns), tuple(col[1] for col in columns)) osc-lib-1.9.0/osc_lib/clientmanager.py0000666000175100017510000002642613227377263017722 0ustar zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # # 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. # """Manage access to the clients, including authenticating when needed.""" import copy import logging import sys from openstack import connection from oslo_utils import strutils import six from osc_lib.api import auth from osc_lib import exceptions from osc_lib import session as osc_session from osc_lib import version # NOTE(dtroyer): Attempt an import to detect if the SDK installed is new # enough to not use Profile. If so, use that. try: from openstack.config import loader as config # noqa profile = None except ImportError: from openstack import profile LOG = logging.getLogger(__name__) PLUGIN_MODULES = [] class ClientCache(object): """Descriptor class for caching created client handles.""" def __init__(self, factory): self.factory = factory self._handle = None def __get__(self, instance, owner): # Tell the ClientManager to login to keystone if self._handle is None: try: self._handle = self.factory(instance) except AttributeError as err: # Make sure the failure propagates. Otherwise, the plugin just # quietly isn't there. new_err = exceptions.PluginAttributeError(err) six.reraise(new_err.__class__, new_err, sys.exc_info()[2]) return self._handle class ClientManager(object): """Manages access to API clients, including authentication.""" # NOTE(dtroyer): Keep around the auth required state of the _current_ # command since ClientManager has no visibility to the # command itself; assume auth is not required. _auth_required = False def __init__( self, cli_options=None, api_version=None, pw_func=None, app_name=None, app_version=None, ): """Set up a ClientManager :param cli_options: Options collected from the command-line, environment, or wherever :param api_version: Dict of API versions: key is API name, value is the version :param pw_func: Callback function for asking the user for a password. The function takes an optional string for the prompt ('Password: ' on None) and returns a string containing the password :param app_name: The name of the application for passing through to the useragent :param app_version: The version of the application for passing through to the useragent """ self._cli_options = cli_options self._api_version = api_version self._pw_callback = pw_func self._app_name = app_name self._app_version = app_version self.region_name = self._cli_options.region_name self.interface = self._cli_options.interface self.timing = self._cli_options.timing self._auth_ref = None self.session = None # self.verify is the Requests-compatible form # self.cacert is the form used by the legacy client libs # self.insecure is not needed, use 'not self.verify' # NOTE(dtroyer): Per bug https://bugs.launchpad.net/bugs/1447784 # --insecure overrides any --os-cacert setting # Set a hard default self.verify = True if self._cli_options.insecure: # Handle --insecure self.verify = False self.cacert = None else: if (self._cli_options.cacert is not None and self._cli_options.cacert != ''): # --cacert implies --verify here self.verify = self._cli_options.cacert self.cacert = self._cli_options.cacert else: # Fall through also gets --verify if self._cli_options.verify is not None: self.verify = self._cli_options.verify self.cacert = None # Set up client certificate and key # NOTE(cbrandily): This converts client certificate/key to requests # cert argument: None (no client certificate), a path # to client certificate or a tuple with client # certificate/key paths. self.cert = self._cli_options.cert if self.cert and self._cli_options.key: self.cert = self.cert, self._cli_options.key # TODO(mordred) The logic above to set all of these is duplicated in # os-client-config but needs more effort to tease apart and ensure that # values are being passed in. For now, let osc_lib do it and just set # the values in occ.config self._cli_options.config['verify'] = self.verify self._cli_options.config['cacert'] = self.cacert # Attack of the killer passthrough values self._cli_options.config['cert'] = self._cli_options.cert self._cli_options.config['key'] = self._cli_options.key # TODO(mordred) We also don't have any support for setting or passing # in api_timeout, which is set in occ defaults but we skip occ defaults # so set it here by hand and later we should potentially expose this # directly to osc self._cli_options.config['api_timeout'] = None # Get logging from root logger root_logger = logging.getLogger('') LOG.setLevel(root_logger.getEffectiveLevel()) # NOTE(gyee): use this flag to indicate whether auth setup has already # been completed. If so, do not perform auth setup again. The reason # we need this flag is that we want to be able to perform auth setup # outside of auth_ref as auth_ref itself is a property. We can not # retrofit auth_ref to optionally skip scope check. Some operations # do not require a scoped token. In those cases, we call setup_auth # prior to dereferrencing auth_ref. self._auth_setup_completed = False def setup_auth(self): """Set up authentication This is deferred until authentication is actually attempted because it gets in the way of things that do not require auth. """ if self._auth_setup_completed: return # Stash the selected auth type self.auth_plugin_name = self._cli_options.config['auth_type'] # Basic option checking to avoid unhelpful error messages auth.check_valid_authentication_options( self._cli_options, self.auth_plugin_name, ) # Horrible hack alert...must handle prompt for null password if # password auth is requested. if (self.auth_plugin_name.endswith('password') and not self._cli_options.auth.get('password')): self._cli_options.auth['password'] = self._pw_callback() LOG.info('Using auth plugin: %s', self.auth_plugin_name) LOG.debug('Using parameters %s', strutils.mask_password(self._cli_options.auth)) self.auth = self._cli_options.get_auth() if self._cli_options.service_provider: self.auth = auth.get_keystone2keystone_auth( self.auth, self._cli_options.service_provider, self._cli_options.remote_project_id, self._cli_options.remote_project_name, self._cli_options.remote_project_domain_id, self._cli_options.remote_project_domain_name ) self.session = osc_session.TimingSession( auth=self.auth, verify=self.verify, cert=self.cert, app_name=self._app_name, app_version=self._app_version, additional_user_agent=[('osc-lib', version.version_string)], ) # Create a common default SDK Connection object if it is the newer # non-Profile style. if profile is None: self.sdk_connection = connection.Connection( config=self._cli_options, session=self.session, ) self._auth_setup_completed = True def validate_scope(self): if self._auth_ref.project_id is not None: # We already have a project scope. return if self._auth_ref.domain_id is not None: # We already have a domain scope. return # We do not have a scoped token (and the user's default project scope # was not implied), so the client needs to be explicitly configured # with a scope. auth.check_valid_authorization_options( self._cli_options, self.auth_plugin_name, ) @property def auth_ref(self): """Dereference will trigger an auth if it hasn't already""" if not self._auth_required: # Forcibly skip auth if we know we do not need it return None if not self._auth_ref: self.setup_auth() LOG.debug("Get auth_ref") self._auth_ref = self.auth.get_auth_ref(self.session) return self._auth_ref def is_service_available(self, service_type): """Check if a service type is in the current Service Catalog""" # Trigger authentication necessary to discover endpoint if self.auth_ref: service_catalog = self.auth_ref.service_catalog else: service_catalog = None # Assume that the network endpoint is enabled. service_available = None if service_catalog: if service_type in service_catalog.get_endpoints(): service_available = True LOG.debug("%s endpoint in service catalog", service_type) else: service_available = False LOG.debug("No %s endpoint in service catalog", service_type) else: LOG.debug("No service catalog") return service_available def get_endpoint_for_service_type(self, service_type, region_name=None, interface='public'): """Return the endpoint URL for the service type.""" if not interface: interface = 'public' # See if we are using password flow auth, i.e. we have a # service catalog to select endpoints from if self.auth_ref: endpoint = self.auth_ref.service_catalog.url_for( service_type=service_type, region_name=region_name, interface=interface, ) else: # Get the passed endpoint directly from the auth plugin endpoint = self.auth.get_endpoint( self.session, interface=interface, ) return endpoint def get_configuration(self): return copy.deepcopy(self._cli_options.config) osc-lib-1.9.0/osc_lib/session.py0000666000175100017510000000313113227377263016560 0ustar zuulzuul00000000000000# 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. # """Subclass of keystoneauth1.session""" from keystoneauth1 import session class TimingSession(session.Session): """A Session that supports collection of timing data per Method URL""" def __init__( self, **kwargs ): """Pass through all arguments except timing""" super(TimingSession, self).__init__(**kwargs) # times is a list of tuples: ("method url", elapsed_time) self.times = [] def get_timings(self): return self.times def reset_timings(self): self.times = [] def request(self, url, method, **kwargs): """Wrap the usual request() method with the timers""" resp = super(TimingSession, self).request(url, method, **kwargs) for h in resp.history: self.times.append(( "%s %s" % (h.request.method, h.request.url), h.elapsed, )) self.times.append(( "%s %s" % (resp.request.method, resp.request.url), resp.elapsed, )) return resp osc-lib-1.9.0/osc_lib/logs.py0000666000175100017510000001570613227377263016054 0ustar zuulzuul00000000000000# 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. # """Application logging""" import logging import sys import warnings def get_loggers(): loggers = {} for logkey in logging.Logger.manager.loggerDict.keys(): loggers[logkey] = logging.getLevelName(logging.getLogger(logkey).level) return loggers def log_level_from_options(options): # if --debug, --quiet or --verbose is not specified, # the default logging level is warning log_level = logging.WARNING if options.verbose_level == 0: # --quiet log_level = logging.ERROR elif options.verbose_level == 2: # One --verbose log_level = logging.INFO elif options.verbose_level >= 3: # Two or more --verbose log_level = logging.DEBUG return log_level def log_level_from_string(level_string): log_level = { 'critical': logging.CRITICAL, 'error': logging.ERROR, 'warning': logging.WARNING, 'info': logging.INFO, 'debug': logging.DEBUG, }.get(level_string, logging.WARNING) return log_level def log_level_from_config(config): # Check the command line option verbose_level = config.get('verbose_level') if config.get('debug', False): verbose_level = 3 if verbose_level == 0: verbose_level = 'error' elif verbose_level == 1: # If a command line option has not been specified, check the # configuration file verbose_level = config.get('log_level', 'warning') elif verbose_level == 2: verbose_level = 'info' else: verbose_level = 'debug' return log_level_from_string(verbose_level) def set_warning_filter(log_level): if log_level == logging.ERROR: warnings.simplefilter("ignore") elif log_level == logging.WARNING: warnings.simplefilter("ignore") elif log_level == logging.INFO: warnings.simplefilter("once") class _FileFormatter(logging.Formatter): """Customize the logging format for logging handler""" _LOG_MESSAGE_BEGIN = ( '%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s ') _LOG_MESSAGE_CONTEXT = '[%(cloud)s %(username)s %(project)s] ' _LOG_MESSAGE_END = '%(message)s' _LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' def __init__(self, options=None, config=None, **kwargs): context = {} if options: context = { 'cloud': getattr(options, 'cloud', ''), 'project': getattr(options, 'os_project_name', ''), 'username': getattr(options, 'username', ''), } elif config: context = { 'cloud': config.config.get('cloud', ''), 'project': config.auth.get('project_name', ''), 'username': config.auth.get('username', ''), } if context: self.fmt = (self._LOG_MESSAGE_BEGIN + (self._LOG_MESSAGE_CONTEXT % context) + self._LOG_MESSAGE_END) else: self.fmt = self._LOG_MESSAGE_BEGIN + self._LOG_MESSAGE_END logging.Formatter.__init__(self, self.fmt, self._LOG_DATE_FORMAT) class LogConfigurator(object): _CONSOLE_MESSAGE_FORMAT = '%(message)s' def __init__(self, options): self.root_logger = logging.getLogger('') self.root_logger.setLevel(logging.DEBUG) # Force verbose_level 3 on --debug self.dump_trace = False if options.debug: options.verbose_level = 3 self.dump_trace = True # Always send higher-level messages to the console via stderr self.console_logger = logging.StreamHandler(sys.stderr) log_level = log_level_from_options(options) self.console_logger.setLevel(log_level) formatter = logging.Formatter(self._CONSOLE_MESSAGE_FORMAT) self.console_logger.setFormatter(formatter) self.root_logger.addHandler(self.console_logger) # Set the warning filter now set_warning_filter(log_level) # Set up logging to a file self.file_logger = None log_file = options.log_file if log_file: self.file_logger = logging.FileHandler(filename=log_file) self.file_logger.setFormatter(_FileFormatter(options=options)) self.file_logger.setLevel(log_level) self.root_logger.addHandler(self.file_logger) # Requests logs some stuff at INFO that we don't want # unless we have DEBUG requests_log = logging.getLogger("requests") # Other modules we don't want DEBUG output for cliff_log = logging.getLogger('cliff') stevedore_log = logging.getLogger('stevedore') iso8601_log = logging.getLogger("iso8601") if options.debug: # --debug forces traceback requests_log.setLevel(logging.DEBUG) else: requests_log.setLevel(logging.ERROR) cliff_log.setLevel(logging.ERROR) stevedore_log.setLevel(logging.ERROR) iso8601_log.setLevel(logging.ERROR) def configure(self, cloud_config): log_level = log_level_from_config(cloud_config.config) set_warning_filter(log_level) self.dump_trace = cloud_config.config.get('debug', self.dump_trace) self.console_logger.setLevel(log_level) log_file = cloud_config.config.get('log_file') if log_file: if not self.file_logger: self.file_logger = logging.FileHandler(filename=log_file) self.file_logger.setFormatter(_FileFormatter(config=cloud_config)) self.file_logger.setLevel(log_level) self.root_logger.addHandler(self.file_logger) logconfig = cloud_config.config.get('logging') if logconfig: highest_level = logging.NOTSET for k in logconfig.keys(): level = log_level_from_string(logconfig[k]) logging.getLogger(k).setLevel(level) if (highest_level < level): highest_level = level self.console_logger.setLevel(highest_level) if self.file_logger: self.file_logger.setLevel(highest_level) # loggers that are not set will use the handler level, so we # need to set the global level for all the loggers for logkey in logging.Logger.manager.loggerDict.keys(): logger = logging.getLogger(logkey) if logger.level == logging.NOTSET: logger.setLevel(log_level) osc-lib-1.9.0/osc_lib/shell.py0000666000175100017510000004351013227377263016211 0ustar zuulzuul00000000000000# Copyright 2012-2013 OpenStack Foundation # Copyright 2015 Dean Troyer # # 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. # """Command-line interface to the OpenStack APIs""" import getpass import locale import logging import sys import traceback from cliff import app from cliff import command from cliff import complete from cliff import help from oslo_utils import importutils from oslo_utils import strutils import six from osc_lib.cli import client_config as cloud_config from osc_lib import clientmanager from osc_lib.command import commandmanager from osc_lib.command import timing from osc_lib import exceptions as exc from osc_lib.i18n import _ from osc_lib import logs from osc_lib import utils osprofiler_profiler = importutils.try_import("osprofiler.profiler") DEFAULT_DOMAIN = 'default' def prompt_for_password(prompt=None): """Prompt user for a password Prompt for a password if stdin is a tty. """ if not prompt: prompt = 'Password: ' pw = None # If stdin is a tty, try prompting for the password if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty(): # Check for Ctl-D try: pw = getpass.getpass(prompt) except EOFError: pass # No password because we did't have a tty or nothing was entered if not pw: raise exc.CommandError(_("No password entered, or found via" " --os-password or OS_PASSWORD"),) return pw class OpenStackShell(app.App): CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' log = logging.getLogger(__name__) timing_data = [] def __init__( self, description=None, version=None, command_manager=None, stdin=None, stdout=None, stderr=None, interactive_app_factory=None, deferred_help=False, ): # Patch command.Command to add a default auth_required = True command.Command.auth_required = True # Some commands do not need authentication help.HelpCommand.auth_required = False complete.CompleteCommand.auth_required = False # Slight change to the meaning of --debug self.DEFAULT_DEBUG_VALUE = None self.DEFAULT_DEBUG_HELP = 'Set debug logging and traceback on errors.' # Do default for positionals if not command_manager: cm = commandmanager.CommandManager('openstack.cli') else: cm = command_manager super(OpenStackShell, self).__init__( description=__doc__.strip(), version=version, command_manager=cm, deferred_help=True, ) # Until we have command line arguments parsed, dump any stack traces self.dump_stack_trace = True # Set in subclasses self.api_version = None self.client_manager = None self.command_options = None self.do_profile = False def configure_logging(self): """Configure logging for the app.""" self.log_configurator = logs.LogConfigurator(self.options) self.dump_stack_trace = self.log_configurator.dump_trace def run(self, argv): ret_val = 1 self.command_options = argv try: ret_val = super(OpenStackShell, self).run(argv) return ret_val except Exception as e: if not logging.getLogger('').handlers: logging.basicConfig() if self.dump_stack_trace: self.log.error(traceback.format_exc()) else: self.log.error('Exception raised: ' + str(e)) return ret_val finally: self.log.info("END return value: %s", ret_val) def init_profile(self): self.do_profile = osprofiler_profiler and self.options.profile if self.do_profile: osprofiler_profiler.init(self.options.profile) def close_profile(self): if self.do_profile: trace_id = osprofiler_profiler.get().get_base_id() # NOTE(dbelova): let's use warning log level to see these messages # printed. In fact we can define custom log level here with value # bigger than most big default one (CRITICAL) or something like # that (PROFILE = 60 for instance), but not sure we need it here. self.log.warning("Trace ID: %s" % trace_id) self.log.warning("Display trace with command:\n" "osprofiler trace show --html %s " % trace_id) def run_subcommand(self, argv): self.init_profile() try: ret_value = super(OpenStackShell, self).run_subcommand(argv) finally: self.close_profile() return ret_value def interact(self): self.init_profile() try: ret_value = super(OpenStackShell, self).interact() finally: self.close_profile() return ret_value def build_option_parser(self, description, version): parser = super(OpenStackShell, self).build_option_parser( description, version) # service token auth argument parser.add_argument( '--os-cloud', metavar='', dest='cloud', default=utils.env('OS_CLOUD'), help=_('Cloud name in clouds.yaml (Env: OS_CLOUD)'), ) # Global arguments parser.add_argument( '--os-region-name', metavar='', dest='region_name', default=utils.env('OS_REGION_NAME'), help=_('Authentication region name (Env: OS_REGION_NAME)'), ) parser.add_argument( '--os-cacert', metavar='', dest='cacert', default=utils.env('OS_CACERT', default=None), help=_('CA certificate bundle file (Env: OS_CACERT)'), ) parser.add_argument( '--os-cert', metavar='', dest='cert', default=utils.env('OS_CERT'), help=_('Client certificate bundle file (Env: OS_CERT)'), ) parser.add_argument( '--os-key', metavar='', dest='key', default=utils.env('OS_KEY'), help=_('Client certificate key file (Env: OS_KEY)'), ) verify_group = parser.add_mutually_exclusive_group() verify_group.add_argument( '--verify', action='store_true', default=None, help=_('Verify server certificate (default)'), ) verify_group.add_argument( '--insecure', action='store_true', default=None, help=_('Disable server certificate verification'), ) parser.add_argument( '--os-default-domain', metavar='', dest='default_domain', default=utils.env( 'OS_DEFAULT_DOMAIN', default=DEFAULT_DOMAIN), help=_('Default domain ID, default=%s. ' '(Env: OS_DEFAULT_DOMAIN)') % DEFAULT_DOMAIN, ) parser.add_argument( '--os-interface', metavar='', dest='interface', choices=['admin', 'public', 'internal'], default=utils.env('OS_INTERFACE'), help=_('Select an interface type.' ' Valid interface types: [admin, public, internal].' ' (Env: OS_INTERFACE)'), ) parser.add_argument( '--os-service-provider', metavar='', dest='service_provider', default=utils.env('OS_SERVICE_PROVIDER'), help=_('Authenticate with and perform the command on a service' ' provider using Keystone-to-keystone federation. Must' ' also specify the remote project option.') ) remote_project_group = parser.add_mutually_exclusive_group() remote_project_group.add_argument( '--os-remote-project-name', metavar='', dest='remote_project_name', default=utils.env('OS_REMOTE_PROJECT_NAME'), help=_('Project name when authenticating to a service provider' ' if using Keystone-to-Keystone federation.') ) remote_project_group.add_argument( '--os-remote-project-id', metavar='', dest='remote_project_id', default=utils.env('OS_REMOTE_PROJECT_ID'), help=_('Project ID when authenticating to a service provider' ' if using Keystone-to-Keystone federation.') ) remote_project_domain_group = parser.add_mutually_exclusive_group() remote_project_domain_group.add_argument( '--os-remote-project-domain-name', metavar='', dest='remote_project_domain_name', default=utils.env('OS_REMOTE_PROJECT_DOMAIN_NAME'), help=_('Domain name of the project when authenticating to a' ' service provider if using Keystone-to-Keystone' ' federation.') ) remote_project_domain_group.add_argument( '--os-remote-project-domain-id', metavar='', dest='remote_project_domain_id', default=utils.env('OS_REMOTE_PROJECT_DOMAIN_ID'), help=_('Domain ID of the project when authenticating to a' ' service provider if using Keystone-to-Keystone' ' federation.') ) parser.add_argument( '--timing', default=False, action='store_true', help=_("Print API call timing info"), ) parser.add_argument( '--os-beta-command', action='store_true', help=_("Enable beta commands which are subject to change"), ) # osprofiler HMAC key argument if osprofiler_profiler: parser.add_argument( '--os-profile', metavar='hmac-key', dest='profile', default=utils.env('OS_PROFILE'), help=_('HMAC key for encrypting profiling context data'), ) return parser # return clientmanager.build_plugin_option_parser(parser) """ Break up initialize_app() so that overriding it in a subclass does not require duplicating a lot of the method * super() * _final_defaults() * OpenStackConfig * get_one_cloud * _load_plugins() * _load_commands() * ClientManager """ def _final_defaults(self): # Set the default plugin to None # NOTE(dtroyer): This is here to set up for setting it to a default # in the calling CLI self._auth_type = None # Converge project/tenant options project_id = getattr(self.options, 'project_id', None) project_name = getattr(self.options, 'project_name', None) tenant_id = getattr(self.options, 'tenant_id', None) tenant_name = getattr(self.options, 'tenant_name', None) # handle some v2/v3 authentication inconsistencies by just acting like # both the project and tenant information are both present. This can # go away if we stop registering all the argparse options together. if project_id and not tenant_id: self.options.tenant_id = project_id if project_name and not tenant_name: self.options.tenant_name = project_name if tenant_id and not project_id: self.options.project_id = tenant_id if tenant_name and not project_name: self.options.project_name = tenant_name # Save default domain self.default_domain = self.options.default_domain def _load_plugins(self): """Load plugins via stevedore osc-lib has no opinion on what plugins should be loaded """ pass def _load_commands(self): """Load commands via cliff/stevedore osc-lib has no opinion on what commands should be loaded """ pass def initialize_app(self, argv): """Global app init bits: * set up API versions * validate authentication info * authenticate against Identity if requested """ # Parent __init__ parses argv into self.options super(OpenStackShell, self).initialize_app(argv) self.log.info("START with options: %s", strutils.mask_password(self.command_options)) self.log.debug("options: %s", strutils.mask_password(self.options)) # Callout for stuff between superclass init and o-c-c self._final_defaults() # Do configuration file handling # Ignore the default value of interface. Only if it is set later # will it be used. try: self.cloud_config = cloud_config.OSC_Config( override_defaults={ 'interface': None, 'auth_type': self._auth_type, }, ) except (IOError, OSError) as e: self.log.critical("Could not read clouds.yaml configuration file") self.print_help_if_requested() raise e # TODO(thowe): Change cliff so the default value for debug # can be set to None. if not self.options.debug: self.options.debug = None # NOTE(dtroyer): Need to do this with validate=False to defer the # auth plugin handling to ClientManager.setup_auth() self.cloud = self.cloud_config.get_one_cloud( cloud=self.options.cloud, argparse=self.options, validate=False, ) self.log_configurator.configure(self.cloud) self.dump_stack_trace = self.log_configurator.dump_trace self.log.debug("defaults: %s", self.cloud_config.defaults) self.log.debug("cloud cfg: %s", strutils.mask_password(self.cloud.config)) # Callout for stuff between o-c-c and ClientManager # self._initialize_app_2(self.options) self._load_plugins() self._load_commands() # Handle deferred help and exit self.print_help_if_requested() self.client_manager = clientmanager.ClientManager( cli_options=self.cloud, api_version=self.api_version, pw_func=prompt_for_password, ) def prepare_to_run_command(self, cmd): """Set up auth and API versions""" self.log.info( 'command: %s -> %s.%s (auth=%s)', getattr(cmd, 'cmd_name', ''), cmd.__class__.__module__, cmd.__class__.__name__, cmd.auth_required, ) # NOTE(dtroyer): If auth is not required for a command, skip # get_one_Cloud()'s validation to avoid loading plugins validate = cmd.auth_required # NOTE(dtroyer): Save the auth required state of the _current_ command # in the ClientManager self.client_manager._auth_required = cmd.auth_required # Validate auth options self.cloud = self.cloud_config.get_one_cloud( cloud=self.options.cloud, argparse=self.options, validate=validate, ) # Push the updated args into ClientManager self.client_manager._cli_options = self.cloud if cmd.auth_required: self.client_manager.setup_auth() if hasattr(cmd, 'required_scope') and cmd.required_scope: # let the command decide whether we need a scoped token self.client_manager.validate_scope() # Trigger the Identity client to initialize self.client_manager.auth_ref return def clean_up(self, cmd, result, err): self.log.debug('clean_up %s: %s', cmd.__class__.__name__, err or '') # Process collected timing data if self.options.timing: # Get session data self.timing_data.extend( self.client_manager.session.get_timings(), ) # Use the Timing pseudo-command to generate the output tcmd = timing.Timing(self, self.options) tparser = tcmd.get_parser('Timing') # If anything other than prettytable is specified, force csv format = 'table' # Check the formatter used in the actual command if hasattr(cmd, 'formatter') \ and cmd.formatter != cmd._formatter_plugins['table'].obj: format = 'csv' sys.stdout.write('\n') targs = tparser.parse_args(['-f', format]) tcmd.run(targs) def main(argv=None): if argv is None: argv = sys.argv[1:] if six.PY2: # Emulate Py3, decode argv into Unicode based on locale so that # commands always see arguments as text instead of binary data encoding = locale.getpreferredencoding() if encoding: argv = map(lambda arg: arg.decode(encoding), argv) return OpenStackShell().run(argv) if __name__ == "__main__": sys.exit(main(sys.argv[1:])) osc-lib-1.9.0/setup.cfg0000666000175100017510000000157213227377613014740 0ustar zuulzuul00000000000000[metadata] name = osc-lib summary = OpenStackClient Library description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://docs.openstack.org/osc-lib/latest/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 [files] packages = osc_lib [build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 warning-is-error = 1 [pbr] autodoc_index_modules = True api_doc_dir = reference/api autodoc_exclude_modules = osc_lib.tests.* [egg_info] tag_build = tag_date = 0 osc-lib-1.9.0/releasenotes/0000775000175100017510000000000013227377613015601 5ustar zuulzuul00000000000000osc-lib-1.9.0/releasenotes/notes/0000775000175100017510000000000013227377613016731 5ustar zuulzuul00000000000000osc-lib-1.9.0/releasenotes/notes/shell-argv-decode-cdc13dc0c4ec07af.yaml0000666000175100017510000000023213227377263025601 0ustar zuulzuul00000000000000--- fixes: - | Decode argv into Unicode on Python 2 in ``OpenStackShell.main()`` [OSC Bug `1603494 `_] osc-lib-1.9.0/releasenotes/notes/.placeholder0000666000175100017510000000000013227377263021205 0ustar zuulzuul00000000000000osc-lib-1.9.0/releasenotes/notes/bug-1558690-1528b637f2c0a449.yaml0000666000175100017510000000031213227377263023304 0ustar zuulzuul00000000000000--- fixes: - | Prevent null key setting for key-value pairs in the ``KeyValueAction`` and ``MultiKeyValueAction`` parser actions. [Bug `1558690 `_]osc-lib-1.9.0/releasenotes/notes/bug-1630822-mask-password-on-debug-20dcdf1c54e84fa1.yaml0000666000175100017510000000037013227377263030055 0ustar zuulzuul00000000000000--- security: - | This release contains the fix for `bug 1630822`_ so that passwords are no longer leaked when using the ``--debug`` or ``-vv`` options. .. _bug 1630822: https://bugs.launchpad.net/python-openstackclient/+bug/1630822 osc-lib-1.9.0/releasenotes/notes/add-MultiKeyValueCommaAction-class-01dd254a287d70d2.yaml0000666000175100017510000000032713227377263030452 0ustar zuulzuul00000000000000--- feature: - | Add ``MultiKeyValueCommaAction`` as a ``MultiKeyValueAction`` sublass that allows values to include a comma. For example: ``--property key1=value1,value2,key2=value3,value4,value5``. osc-lib-1.9.0/releasenotes/notes/1.0-summary-47dcce446d6a512b.yaml0000666000175100017510000000522313227377263024105 0ustar zuulzuul00000000000000--- prelude: > ``osc-lib`` was extracted from the main OpenStackClient repo after the OSC 2.4.0 release. A number of the lower-layer modules were simply renamed into the osc_lib namespace:: * openstackclient.api.api -> osc_lib.api.api * openstackclient.api.auth -> osc_lib.api.auth * openstackclient.api.utils -> osc_lib.api.utils * openstackclient.common.command -> osc_lib.command.command * openstackclient.common.exceptions -> osc_lib.exceptions * openstackclient.common.logs -> osc_lib.logs * openstackclient.common.parseractions -> osc_lib.cli.parseractions * openstackclient.common.session -> osc_lib.session * openstackclient.common.utils -> osc_lib.utils * openstackclient.i18n -> osc_lib.i18n The higher-layer components, such as the OpenStackShell and ClientManager objects, have had significant changes made to them to streamline interaction with ``os-client-config`` and ``keystoneauth`` in addition to the rename:: * openstackclient.common.commandmanager -> osc_lib.command.commandmanager * openstackclient.shell -> osc_lib.shell features: - Add ``utils.find_min_match()`` function to filter a list based on a set of minimum values of attributes. For example, selecting all compute flavors that have a minimum amount of RAM and disk and VCPUs. - Add ``cli.client_config.OSC_Config`` as a subclass of ``os_client_config.config.OpenStackConfig`` to collect all of the configuration option special cases in OSC into one place and insert into the ``os-client-config`` handling. fixes: - The ``parseractions.KeyValueAction`` class now raises a ``argparse.ArgumentTypeError`` exception when the argument is not in the form ``=``. - Change ``utils.find_resource()`` to handle client managers that lack a ``find()`` method. Raise an ``exceptions.CommandError`` exception when multiple matches are found. - Change ``utils.find_resource()`` to handle glanceclient's ``HTTPNotFound`` exception. - Change ``utils.find_resource()`` to attempt lookups as IDs first, falling back to ``find()`` methods when available. - Refactor ``ClientManager`` class to remove OSC-specific logic and move all option special-cases into ``cli.client_config.OSC_Config``. Also change some private attributes to public (``region_name``, ``interface``, ``cacert``, ``verify`` and remove ``_insecure``). - Refactor ``OpenStackShell`` to handle only global argument processing and setting up the ClientManager with configuration from ``os-client-config``. Command and plugin loading remain in OSC. osc-lib-1.9.0/releasenotes/notes/keystone-to-keystone-9b2e55b051775322.yaml0000666000175100017510000000113213227377263025730 0ustar zuulzuul00000000000000--- features: - | Added new options `--os-service-provider`, `--os-remote-project-name`, `--os-remote-project-domain-name`, `--os-remote-project-domain-id`, and `--os-remote-project-id`. These can also be loaded from their respective ENV variables (ex. `OS_SERVICE_PROVIDER` and `OS_REMOTE_PROJECT_ID`) and allow issuing of commands to a federated cloud using keystone-to-keystone identity federation. More information about keystone-to-keystone can be found `here `_. osc-lib-1.9.0/releasenotes/notes/os-profile-as-environment-variable-a5e232e9ca7c5171.yaml0000666000175100017510000000025413227377263030637 0ustar zuulzuul00000000000000--- features: - | ``--os-profile`` argument can be loaded from ``OS_PROFILE`` environment variables to avoid repeating ``--os-profile`` in openstack commands.osc-lib-1.9.0/releasenotes/notes/arg-precedence-1ba9fd6929650830.yaml0000666000175100017510000000041213227377263024545 0ustar zuulzuul00000000000000--- fixes: - | Add additional precedence fixes to the argument precedence problems in os-client-config 1.18.0 and earlier. This all will be removed when os-client-config 1.19.x is the minimum allwed version in OpenStack's global requirements.txt. osc-lib-1.9.0/releasenotes/source/0000775000175100017510000000000013227377613017101 5ustar zuulzuul00000000000000osc-lib-1.9.0/releasenotes/source/ocata.rst0000666000175100017510000000023013227377263020720 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata osc-lib-1.9.0/releasenotes/source/newton.rst0000666000175100017510000000021613227377263021147 0ustar zuulzuul00000000000000============================= Newton Series Release Notes ============================= .. release-notes:: :branch: origin/stable/newton osc-lib-1.9.0/releasenotes/source/index.rst0000666000175100017510000000032413227377263020744 0ustar zuulzuul00000000000000===================== osc-lib Release Notes ===================== .. toctree:: :maxdepth: 1 unreleased pike ocata newton Indices and tables ================== * :ref:`genindex` * :ref:`search` osc-lib-1.9.0/releasenotes/source/pike.rst0000666000175100017510000000021713227377263020566 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike osc-lib-1.9.0/releasenotes/source/_static/0000775000175100017510000000000013227377613020527 5ustar zuulzuul00000000000000osc-lib-1.9.0/releasenotes/source/_static/.placeholder0000666000175100017510000000000013227377263023003 0ustar zuulzuul00000000000000osc-lib-1.9.0/releasenotes/source/_templates/0000775000175100017510000000000013227377613021236 5ustar zuulzuul00000000000000osc-lib-1.9.0/releasenotes/source/_templates/.placeholder0000666000175100017510000000000013227377263023512 0ustar zuulzuul00000000000000osc-lib-1.9.0/releasenotes/source/conf.py0000666000175100017510000002350513227377263020410 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # 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. # osc-lib Release Notes documentation build configuration file, created by # sphinx-quickstart on Thu Jul 28 17:16:41 2016. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. import openstackdocstheme # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'reno.sphinxext', 'sphinx.ext.extlinks', ] # Set aliases for extlinks # * lpbug - generic Launchpad bug :lpbug:`123456` # * oscbp - OSC blueprints :oscbp:`Blue Print ` # * oscdoc - OSC Docs :oscdoc:`Comamnd List ` extlinks = { 'lpbug': ( 'https://bugs.launchpad.net/bugs/%s', 'Bug ', ), 'oscbp': ( 'https://blueprints.launchpad.net/python-openstackclient/+spec/%s', '', ), 'oscdoc': ( 'http://docs.openstack.org/developer/osc-lib/%s.html', '', ), } # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'osc-lib Release Notes' copyright = u'2016, osc-lib Developers' # Release notes do not need a version in the title, they span # multiple versions. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] html_theme_path = [openstackdocstheme.get_html_theme_path()] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'OSC_LIBReleaseNotesdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [( 'index', 'OSC_LIBReleaseNotes.tex', u'osc-lib Release Notes Documentation', u'osc-lib Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [( 'index', 'osc_libreleasenotes', u'osc-lib Release Notes Documentation', [u'osc-lib Developers'], 1, )] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [( 'index', 'OSC_LIBReleaseNotes', u'osc-lib Release Notes Documentation', u'osc-lib Developers', 'OSC_LIBReleaseNotes', 'Common base library for OpenStackClient plugins.', 'Miscellaneous', )] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] osc-lib-1.9.0/releasenotes/source/unreleased.rst0000666000175100017510000000012613227377263021764 0ustar zuulzuul00000000000000===================== Current Release Notes ===================== .. release-notes:: osc-lib-1.9.0/README.rst0000666000175100017510000000460713227377263014611 0ustar zuulzuul00000000000000======= osc-lib ======= .. image:: https://img.shields.io/pypi/v/osc-lib.svg :target: https://pypi.python.org/pypi/osc-lib/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/osc-lib.svg :target: https://pypi.python.org/pypi/osc-lib/ :alt: Downloads OpenStackClient (aka OSC) is a command-line client for OpenStack. osc-lib is a package of common support modules for writing OSC plugins. * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - part of OpenStackClient * `Bugs`_ - issue tracking * `Source`_ * `Developer` - getting started as a developer * `Contributing` - contributing code * `Testing` - testing code * IRC: #openstack-sdks on Freenode (irc.freenode.net) * License: Apache 2.0 .. _PyPi: https://pypi.python.org/pypi/osc-lib .. _Online Documentation: http://docs.openstack.org/osc-lib/latest/ .. _Launchpad project: https://launchpad.net/python-openstackclient .. _Bugs: https://bugs.launchpad.net/python-openstackclient .. _Source: https://git.openstack.org/cgit/openstack/osc-lib .. _Developer: http://docs.openstack.org/project-team-guide/project-setup/python.html .. _Contributing: http://docs.openstack.org/infra/manual/developers.html .. _Testing: http://docs.openstack.org/osc-lib/latest/contributor/#testing Getting Started =============== osc-lib can be installed from PyPI using pip:: pip install osc-lib Transition From OpenStackclient =============================== This library was extracted from the main OSC repo after the OSC 2.4.0 release. The following are the changes to imports that will cover the majority of transition to using osc-lib: * openstackclient.api.api -> osc_lib.api.api * openstackclient.api.auth -> osc_lib.api.auth * openstackclient.api.utils -> osc_lib.api.utils * openstackclient.common.command -> osc_lib.command.command * openstackclient.common.commandmanager -> osc_lib.command.commandmanager * openstackclient.common.exceptions -> osc_lib.exceptions * openstackclient.common.logs -> osc_lib.logs * openstackclient.common.parseractions -> osc_lib.cli.parseractions * openstackclient.common.session -> osc_lib.session * openstackclient.common.utils -> osc_lib.utils * openstackclient.i18n -> osc_lib.i18n * openstackclient.shell -> osc_lib.shell Also, some of the test fixtures and modules may be used: * openstackclient.tests.fakes -> osc_lib.tests.fakes * openstackclient.tests.utils -> osc_lib.tests.utils osc-lib-1.9.0/test-requirements.txt0000666000175100017510000000117113227377263017354 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking<0.11,>=0.10.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0.0 # BSD oslotest>=1.10.0 # Apache-2.0 requests-mock>=1.1.0 # Apache-2.0 sphinx>=1.6.2 # BSD os-testr>=1.0.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=2.2.0 # MIT osprofiler>=1.4.0 # Apache-2.0 bandit>=1.1.0 # Apache-2.0 # Documentation openstackdocstheme>=1.17.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 osc-lib-1.9.0/.mailmap0000666000175100017510000000014313227377263014532 0ustar zuulzuul00000000000000 osc-lib-1.9.0/AUTHORS0000664000175100017510000001160513227377612014162 0ustar zuulzuul00000000000000Aaron Rosen Abhishek Chanda Akihiro Motoki Akihiro Motoki Alessandro Pilotti Alessio Ababilov Alex Gaynor Alex Schultz Alexander Ignatov Alexander Tsamutali Alvaro Lopez Garcia Amey Bhide Andreas Jaeger Andreas Jaeger Atsushi SAKAI Bhuvan Arumugam Brad Behle Brandon Palm Cao Xuan Hoang Cedric Brandily Chaozhe.Chen Christian Berendt Chuck Short Clark Boylan Colleen Murphy Cyril Roelandt Daisuke Fujita Daniel Gonzalez Dao Cong Tien Dave Chen David Moreau Simard Dean Troyer Dina Belova Doug Hellmann Doug Hellmann Dougal Matthews Eric Brown Florent Flament Guojian Shao Hangdong Zhang Hidekazu Nakamura Hideki Saito Hieu LE Hironori Shiina Huanxuan Ao Hugh Saunders Igor_Bolotin Ilya Shakhat Jake Yip James E. Blair Jamie Lennox Jamie Lennox Jas Javier Pena Jeremy Stanley Jerry George Jesse Keating Joe Gordon Josh Kearney Joshua Harlow Juan Antonio Osorio Robles Jude Job Julien Lavesque Ken Thomas Kristi Nikolla Kyrylo Romanenko Luong Anh Tuan Marek Aufart Marek Denis Mark Vanderwiel Matt Fischer Matt Joyce Matt Riedemann Matthew Treinish Matthieu Huin Michael McCune Monty Taylor Mouad Benchchaoui Nam Nguyen Hoai Noorul Islam K M Oleksii Chuprykov Ondřej Nový OpenStack Release Bot Paul Belanger Qiu Yu Reedip Richard Theis Rodrigo Duarte Sousa Roey Chen Roxana Gherle Rui Chen SaiKiran Sascha Peilicke Sascha Peilicke Sheel Rana Sirushti Murugesan Steve Martinelli Steve Martinelli Swapnil Kulkarni (coolsvap) Tang Chen Tang Chen Terry Howe TerryHowe Thomas Bechtold Thomas Goirand Tim Burke Tom Cocozzello Tony Breeds Tovin Seven Ukesh Kumar Vasudevan Wenzhi Yu Xi Yang Zhenguo Niu Zuul chengkunye guang-yee heha henriquetruta jiaxi jichenjc kavithahr lin-hua-cheng luqitao qtang rabi reedip sunyajing ting.wang venkatamahesh wanghong xiaozhuangqing xiexs zhiyuan_cai zhurong osc-lib-1.9.0/.stestr.conf0000666000175100017510000000013013227377263015356 0ustar zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./osc_lib/tests} top_dir=./ group_regex=([^\.]+\.)+ osc-lib-1.9.0/.coveragerc0000666000175100017510000000010413227377262015226 0ustar zuulzuul00000000000000[run] branch = True source = osc_lib [report] ignore_errors = True osc-lib-1.9.0/HACKING.rst0000666000175100017510000000602713227377263014716 0ustar zuulzuul00000000000000OpenStack Style Commandments ============================ - Step 1: Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/user/hacking.html - Step 2: Read on General ------- - thou shalt not violate causality in our time cone, or else Docstrings ---------- Docstrings should ONLY use triple-double-quotes (``"""``) Single-line docstrings should NEVER have extraneous whitespace between enclosing triple-double-quotes. Deviation! Sentence fragments do not have punctuation. Specifically in the command classes the one line docstring is also the help string for that command and those do not have periods. """A one line docstring looks like this""" Calling Methods --------------- Deviation! When breaking up method calls due to the 79 char line length limit, use the alternate 4 space indent. With the first argument on the succeeding line all arguments will then be vertically aligned. Use the same convention used with other data structure literals and terminate the method call with the last argument line ending with a comma and the closing paren on its own line indented to the starting line level. unnecessarily_long_function_name( 'string one', 'string two', kwarg1=constants.ACTIVE, kwarg2=['a', 'b', 'c'], ) Text encoding ------------- Note: this section clearly has not been implemented in this project yet, it is the intention to do so. All text within python code should be of type 'unicode'. WRONG: >>> s = 'foo' >>> s 'foo' >>> type(s) RIGHT: >>> u = u'foo' >>> u u'foo' >>> type(u) Transitions between internal unicode and external strings should always be immediately and explicitly encoded or decoded. All external text that is not explicitly encoded (database storage, commandline arguments, etc.) should be presumed to be encoded as utf-8. WRONG: infile = open('testfile', 'r') mystring = infile.readline() myreturnstring = do_some_magic_with(mystring) outfile.write(myreturnstring) RIGHT: infile = open('testfile', 'r') mystring = infile.readline() mytext = mystring.decode('utf-8') returntext = do_some_magic_with(mytext) returnstring = returntext.encode('utf-8') outfile.write(returnstring) Python 3.x Compatibility ------------------------ OpenStackClient strives to be Python 3.3 compatible. Common guidelines: * Convert print statements to functions: print statements should be converted to an appropriate log or other output mechanism. * Use six where applicable: x.iteritems is converted to six.iteritems(x) for example. Running Tests ------------- Note: Oh boy, are we behind on writing tests. But they are coming! The testing system is based on a combination of tox and testr. If you just want to run the whole suite, run `tox` and all will be fine. However, if you'd like to dig in a bit more, you might want to learn some things about testr itself. A basic walkthrough for OpenStack can be found at http://wiki.openstack.org/testr osc-lib-1.9.0/requirements.txt0000666000175100017510000000104213227377263016374 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 keystoneauth1>=3.3.0 # Apache-2.0 openstacksdk>=0.9.19 # Apache-2.0 os-client-config>=1.28.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 simplejson>=3.5.1 # MIT stevedore>=1.20.0 # Apache-2.0 osc-lib-1.9.0/setup.py0000666000175100017510000000200613227377263014623 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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. # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools # In python < 2.7.4, a lazy loading of package `pbr` will break # setuptools if some other modules registered functions in `atexit`. # solution from: http://bugs.python.org/issue15881#msg170215 try: import multiprocessing # noqa except ImportError: pass setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) osc-lib-1.9.0/doc/0000775000175100017510000000000013227377613013655 5ustar zuulzuul00000000000000osc-lib-1.9.0/doc/Makefile0000666000175100017510000001135313227377263015323 0ustar zuulzuul00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html pdf dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " pdf to make pdf with rst2pdf" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." pdf: $(SPHINXBUILD) -b pdf $(ALLSPHINXOPTS) $(BUILDDIR)/pdf @echo @echo "Build finished. The PDFs are in $(BUILDDIR)/pdf." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/NebulaDocs.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/NebulaDocs.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/NebulaDocs" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/NebulaDocs" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." osc-lib-1.9.0/doc/ext/0000775000175100017510000000000013227377613014455 5ustar zuulzuul00000000000000osc-lib-1.9.0/doc/ext/__init__.py0000666000175100017510000000000013227377263016557 0ustar zuulzuul00000000000000osc-lib-1.9.0/doc/ext/apidoc.py0000666000175100017510000000263513227377263016277 0ustar zuulzuul00000000000000# Copyright 2014 OpenStack Foundation # # 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 os.path as path from sphinx import apidoc # NOTE(blk-u): pbr will run Sphinx multiple times when it generates # documentation. Once for each builder. To run this extension we use the # 'builder-inited' hook that fires at the beginning of a Sphinx build. # We use ``run_already`` to make sure apidocs are only generated once # even if Sphinx is run multiple times. run_already = False def run_apidoc(app): global run_already if run_already: return run_already = True package_dir = path.abspath(path.join(app.srcdir, '..', '..', 'osc_lib')) source_dir = path.join(app.srcdir, 'api') apidoc.main(['apidoc', package_dir, '-f', '-H', 'osc-lib Modules', '-o', source_dir]) def setup(app): app.connect('builder-inited', run_apidoc) osc-lib-1.9.0/doc/source/0000775000175100017510000000000013227377613015155 5ustar zuulzuul00000000000000osc-lib-1.9.0/doc/source/contributor/0000775000175100017510000000000013227377613017527 5ustar zuulzuul00000000000000osc-lib-1.9.0/doc/source/contributor/index.rst0000666000175100017510000000174613227377263021403 0ustar zuulzuul00000000000000============== Contributing ============== osc-lib utilizes all of the usual OpenStack processes and requirements for contributions. The code is hosted `on OpenStack's Git server`_. `Bug reports`_ and `blueprints`_ may be submitted to the :code:`python-openstackclient` project on `Launchpad`_. Code may be submitted to the :code:`openstack/osc-lib` project using `Gerrit`_. Developers may also be found in the `IRC channel`_ ``#openstack-sdks``. .. _`on OpenStack's Git server`: https://git.openstack.org/cgit/openstack/python-openstackclient/tree .. _Launchpad: https://launchpad.net/python-openstackclient .. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow .. _Bug reports: https://bugs.launchpad.net/python-openstackclient/+bugs .. _blueprints: https://blueprints.launchpad.net/python-openstackclient .. _PyPi: https://pypi.python.org/pypi/osc-lib .. _tarball: http://tarballs.openstack.org/osc-lib .. _IRC channel: https://wiki.openstack.org/wiki/IRC osc-lib-1.9.0/doc/source/index.rst0000666000175100017510000000067713227377263017033 0ustar zuulzuul00000000000000========================================= osc-lib -- OpenStackClient Plugin Library ========================================= OpenStackClient (aka OSC) is a command-line client for OpenStack. osc-lib is a package of common support modules for writing OSC plugins. Contents: .. toctree:: :maxdepth: 2 user/index reference/index contributor/index .. rubric:: Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` osc-lib-1.9.0/doc/source/reference/0000775000175100017510000000000013227377613017113 5ustar zuulzuul00000000000000osc-lib-1.9.0/doc/source/reference/index.rst0000666000175100017510000000016713227377263020763 0ustar zuulzuul00000000000000======================= Library API Reference ======================= .. toctree:: :maxdepth: 2 api/autoindex osc-lib-1.9.0/doc/source/user/0000775000175100017510000000000013227377613016133 5ustar zuulzuul00000000000000osc-lib-1.9.0/doc/source/user/transition.rst0000666000175100017510000000743113227377263021067 0ustar zuulzuul00000000000000=============================== Transition from OpenStackClient =============================== ``osc-lib`` was extracted from the main OpenStackClient repo after the OSC 2.4.0 release. During the migration all module names have changed from ``openstackclient.*`` to ``osc_lib.*``. In addition, some re-arranging has been done internally to better align modules. The complete list of public module name changes: * ``openstackclient.api.api`` -> ``osc_lib.api.api`` * ``openstackclient.api.auth`` -> ``osc_lib.api.auth`` * ``openstackclient.api.utils`` -> ``osc_lib.api.utils`` * ``openstackclient.common.command`` -> ``osc_lib.command.command`` * ``openstackclient.common.commandmanager`` -> ``osc_lib.command.commandmanager`` * ``openstackclient.common.exceptions`` -> ``osc_lib.exceptions`` * ``openstackclient.common.logs`` -> ``osc_lib.logs`` * ``openstackclient.common.parseractions`` -> ``osc_lib.cli.parseractions`` * ``openstackclient.common.session`` -> ``osc_lib.session`` * ``openstackclient.common.utils`` -> ``osc_lib.utils`` * ``openstackclient.tests.fakes`` -> ``osc_lib.tests.fakes`` * ``openstackclient.tests.utils`` -> ``osc_lib.tests.utils`` Additional Changes ================== In addition to the existing public modules, other parts of OSC have been extracted, including the base ``Command``, ``CommandManager``, ``ClientManager`` and ``Session`` classes. ClientManager ------------- The OSC ``ClientManager`` is responsible for managing all of the handles to the individual API client objects as well as coordinating session and authentication objects. Plugins are encouraged to use the ClientManager interface for obtaining information about global configuration. * ``openstackclient.common.clientmanager`` -> ``osc_lib.clientmanager`` * All of the handling of the ``verify``/``insecure``/``cacert`` configuration options has been consolidated into ``ClientManager``. This converts the ``--verify``, ``--insecure`` and ``--os-cacert`` options into a ``Requests``-compatible ``verify`` attribute and a ``cacert`` attribute for the legacy client libraries. both are now public; the ``_insecure`` attribute has been removed. .. list-table:: Verify/Insecure/CACert :header-rows: 1 * - --verify - --insecure - --cacert - Result * - None - None - - ``verify=True``, ``cacert=None`` * - True - None - None - ``verify=True``, ``cacert=None`` * - None - True - None - ``verify=False``, ``cacert=None`` * - None - None - - ``verify=cacert``, ``cacert=`` * - True - None - - ``verify=cacert``, ``cacert=`` * - None - True - - ``verify=False``, ``cacert=None`` * A number of other ``ClientManager`` attributes have also been made public to encourage their direct use rather than reaching in to the global options passed in the ``ClientManager`` constructor: * ``_verify`` -> ``verify`` * ``_cacert`` -> ``cacert`` * ``_cert`` -> ``cert`` * ``_insecure`` -> removed, use '`not verify'` * ``_interface`` -> ``interface`` * ``_region_name`` -> ``region_name`` Shell ===== * ``openstackclient.shell`` -> ``osc_lib.shell`` * Break up ``OpenStackShell.initialize_app()`` * leave all plugin initialization in OSC in ``_load_plugins()`` * leave all command loading in OSC in ``_load_commands()`` API === The API base layer is the common point for all API subclasses. It is a wrapper around ``keystoneauth1.session.Session`` that fixes the ``request()`` interface and provides simple endpoint handling that is useful when a Service Catalog is either not available or is insufficient. It also adds simple implementations of the common API CRUD operations: create(), delete(), etc. * ``KeystoneSession`` -> merged into ``BaseAPI`` osc-lib-1.9.0/doc/source/user/index.rst0000666000175100017510000000015213227377263017775 0ustar zuulzuul00000000000000=============== Using osc-lib =============== .. toctree:: :maxdepth: 2 transition change_log osc-lib-1.9.0/doc/source/user/change_log.rst0000666000175100017510000000004013227377263020750 0ustar zuulzuul00000000000000.. include:: ../../../ChangeLog osc-lib-1.9.0/doc/source/conf.py0000666000175100017510000002061713227377263016465 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # # OpenStack Command Line Client documentation build configuration file, created # by sphinx-quickstart on Wed May 16 12:05:58 2012. # # This file is execfile()d with the current directory set to its containing # dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import sys import openstackdocstheme import pbr.version # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) # NOTE(blk-u): Path for our Sphinx extension, remove when # https://launchpad.net/bugs/1260495 is fixed. sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'openstackdocstheme', ] # openstackdocstheme options repository_name = 'openstack/osc-lib' bug_project = 'python-openstackclient' bug_tag = 'osc-lib' html_last_updated_fmt = '%Y-%m-%d %H:%M' # Add any paths that contain templates here, relative to this directory. #templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'OpenStackClient CLI Base' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # version_info = pbr.version.VersionInfo('osc-lib') # # The short X.Y version. version = version_info.version_string() # The full version, including alpha/beta/rc tags. release = version_info.release_string() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['osc_lib.'] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. #html_theme_path = ["."] #html_theme = '_theme' html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] html_theme_path = [openstackdocstheme.get_html_theme_path()] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'OpenStackCommandLineClientdoc' # -- Options for LaTeX output ------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]) # . latex_documents = [ ('index', 'OpenStackCommandLineClient.tex', u'OpenStack Command Line Client Documentation', u'OpenStack'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). #man_pages = [] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ----------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'OpenStackCommandLineClient', u'OpenStack Command Line Client Documentation', u'OpenStack', 'OpenStackCommandLineClient', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' osc-lib-1.9.0/.testr.conf0000666000175100017510000000056713227377263015211 0ustar zuulzuul00000000000000[DEFAULT] test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./osc_lib/tests} $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list group_regex=([^\.]+\.)+ osc-lib-1.9.0/LICENSE0000666000175100017510000002363713227377263014133 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. osc-lib-1.9.0/ChangeLog0000664000175100017510000007456413227377612014701 0ustar zuulzuul00000000000000CHANGES ======= 1.9.0 ----- * Fix find() interface when attr is not specified * Relocate utils tests to match the main code * Updated from global requirements * Fix sorting in Python 3 1.8.0 ----- * Add utils for better column handling * Updated from global requirements * Use converged SDK insead of os-client-config * Remove -U from pip install * Avoid tox\_install.sh for constraints support * Util to calculate header and attr names based on parsed\_args.columns * Make -tips job non-voting * Remove setting of version/release from releasenotes * Updated from global requirements * Updated from global requirements * Consume the devstack functional jobs from OSC * Migrade legacy jobs into the repo * Add CLI/ENV options and documentation for keystone-to-keystone * Updated from global requirements * --os-profile option suddenly causes trouble in unit tests * Updates for stestr * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Update reno for stable/pike * Updated from global requirements * Update the documentation link for doc migration * Updated from global requirements * Updated from global requirements * Emit warnings to encourage cliff FormattableColumn 1.7.0 ----- * Add test methods to compare formattable column values * Updated from global requirements * use openstackdocstheme html context * update links to docs in readme * switch from oslosphinx to openstackdocstheme * turn on warning-is-error for sphinx * rearrange existing documentation to fit the new standard layout * Optimize find\_resource: do not run the same query twice * Make --os-profile load from environment variables * Deprecate --profile and remove after Apr 2017 * Updated from global requirements 1.6.0 ----- * Fix shell saving OpenStackConfig object * Follow-up to headers handler to fix find\_bulk() * Updated from global requirements * Updated from global requirements 1.5.1 ----- * Tell ClientManager when auth is required * Add endpoint hook to BaseAPI 1.5.0 ----- * Add token auth test * Revert "Avoid to authenticate twice" 1.4.0 ----- * Add MultiKeyValueCommaAction to osc-lib * Add more API support * Add formattable column base classes * Updated from global requirements * Updated from global requirements * Avoid to authenticate twice * Change noauth strategy for plugin loading * Nit: Reorder some util methods in alphabetic order * Using assertIsNone() instead of assertEqual(None) * Remove log translations * Fix find\_resource exception handling on numeric names with kwargs * Util methods for column name backward compatibility * The python 3.5 is added * Updated from global requirements * Updated from global requirements * Pass ssl values through to OCC * Avoid 'NoneType' error when format conversion * Updated from global requirements * Update reno for stable/ocata * Updated from global requirements * Updated from global requirements 1.3.0 ----- * To display image size in human friendly format * Don't call formatters on None value * Include OSC additions 2 * Include OSC additions * Add deprecated\_option\_warning() method to Command * Calls to federated service providers using Keystone-to-Keystone * Add Constraints support * Updated from global requirements * Updated from global requirements * Updated from global requirements * Don't include openstack/common in flake8 exclude list * Fix version import in releasenotes * Remove os-client-config hacks for 1.19.x and 1.20.x * Updated from global requirements * Updated from global requirements * Allow passing app\_name and app\_version for useragent * Avoid string actions on non-string objects * Updated from global requirements * Updated from global requirements * Updated from global requirements 1.2.0 ----- * Add release note for security bug 1630822 * Improve output of supported client versions * Enable release notes translation * Updated from global requirements * Mask passwords in debug logs for auth\_config\_hook * Updated from global requirements * Fix a tiny typo in documentation * Updated from global requirements * Updated from global requirements * TrivilalFix: Using assertIsNone() instead of assertEqual(None) * Updated from global requirements * Update docstring to match params * Incorrect usage message when no auth param passed * standardize release note page ordering * Update reno for stable/newton * Updated from global requirements * Prompt for auth options 1.1.0 ----- * Fix default handling for verify option in ClientManager * Clean imports in code * TrivialFix: Remove logging import unused * Updated from global requirements * Updated from global requirements * Updated from global requirements 1.0.2 ----- * Another precedence fix 1.0.1 ----- * More hacks to fix broken o-c-c precedence 1.0.0 ----- * Fix arguemnt precedence issues with os-client-config * Do not add user domain options if not needed * Updated from global requirements * Decode argv to Unicode on py2 * Prevent null key setting for property * Add release notes for 1.0 release * Updated from global requirements * Updated from global requirements * Add reno for osc-lib release notes management * Updated from global requirements 0.4.1 ----- * Allow shell class to be overridden in test subclass * Remove option handling unused code * Use assertEqual() instead of assertDictEqual() 0.4.0 ----- * Add Python 3.5 classifier and venv * Updated from global requirements * Fix v2 auth with v3 args present * trivial whitespace change to kick off docs publishing * Remove discover from test-requirements * Remove unused releasenotes infrastructure * Remove discover from test-reqs 0.3.0 ----- * Updated from global requirements * Generate auth plugin options based on the name * Remove tempest from test-requirements.txt * Don't create a requests.Session for session * Remove old fakes * Remove setting project name on clientmanager * Updated from global requirements 0.2.1 ----- * Get VersionInfo of "osc-lib" * Attempt to find resource by ID, without kwargs * Make OSC\_Config the default 0.2.0 ----- * Updated from global requirements * update the transition docs * Add OSC\_Config os-client-config subclass * Updated from global requirements * Backport scope defaults fix (bug 1582774) * Backport check\_valid\_auth\_options() fix * Change default auth plugin to 'password' * Backport scoped token fixes (bug 1592062) * Backport i18n fixes (bug 1574965) * Backport TypeError fix (bug 1575787) * Move api/auth.py into osc\_lib * Updated from global requirements * Modify find\_resource to support glanceclient HTTPNotFound exception * Update untils\_find\_resource to support no unique matches error * Updated from global requirements * Update is\_ascii to work with py3 * Clean up foundation copyrights * Add find\_min\_match() * Sort utils.py and test\_utils.py * Fix interface arg to url\_for() * Fix missing i18n supports in osc-lib 0.1.0 ----- * Backport i18n fixes * Backport log fix * Backport --os-beta-command * Error handling for KeyValueAction class * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Change is\_network\_endpoint\_enabled() to is\_service\_available() * Clean up API * Move api.api and api.utils to osc\_lib * Move shell to osc\_lib and begin rework * Add transition doc * Rework TLS option handling * Remove keystoneclient dependency * Move clientmanager to osc\_lib * Updated from global requirements * Updated from global requirements * fix the docs build * Fix imports in remaining openstackclient modules for testing * Begin moving bits to osc\_lib * Make remaining tests pass * Trim requirements.txt and test-requirements.txt * Rename to osc-lib * Implement "address scope set" command * Implement "address scope show" command * Implement "address scope list" command * Implement "address scope delete" command * Implement "address scope create" command * Updated from global requirements * Ignore domain related config when using with keystone v2 * Updated from global requirements * Ignore domain related config when using with keystone v2 * remove assert in favor an if/else * Replace tempest-lib with tempest.lib * add a bandit environment to tox * Support for volume service list * Updated from global requirements * Add "server group show" command * Add "server group list" command * Add "server group delete" command * Add "server group create" command * Fix mutable default arguments in tests * Rename --profile to --os-profile * Updated from global requirements * Updated from global requirements * Propagate AttributeErrors when lazily loading plugins * Updated from global requirements * Move keys() methods in each resource class to FakeResource * Updated from global requirements * Updated from global requirements * Support client certificate/key * Fix typos in docstrings and comments * Use fixtures and addCleanup instead of tearDown * Don't mask authorization errors * Remove unused method 'from\_response' * Refactor security group rule list to use SDK * Add "aggregate unset" to osc * Subnet: Add "subnet set" command using SDK * [Floating IP] Neutron support for "ip floating create" command * Refactor security group rule create to use SDK * Add Subnet add/remove support to router * Add "router remove port" to osc * Add "router add port" to osc * Updated from global requirements * update docs with status of plugins * Updated from global requirements * Use assertItemsEqual() instead of assertListEqual() * Fix dict.keys() compatibility for python 3 * Add "os subnet create" command using SDK * Refactor security group create to use SDK * Refactor security group show to use SDK * Add 'port set' command * [Subnet pool] Add 'subnet pool create' command support * [Subnet pool] Add 'subnet pool set' command support * remove py26 workaround in osc * Add port list command * Trivial: Remove useless return * Updated from global requirements * Add 'port create' command * Updated from global requirements * Updated from global requirements * Refactor security group set to use SDK * Updated from global requirements * Fix regression in interactive client mode * Subnet: Add "subnet delete" command using SDK * fix: Exception message includes unnecessary class args * Refactor security group list to use SDK * Add MultiKeyValueAction to custom parser action * Updated from global requirements * [compute] Add set host command * Add shell --profile option to trigger osprofiler from CLI * Floating IP: Neutron support for "ip floating show" command * Improve tox to show coverage report on same window * Updated from global requirements * Defaults are ignored with flake8 * Fixed a bunch of spacing * Add "security group rule show" command * [compute] Support restore server * Use instanceof instead of type * Add "os subnet show" command using SDK * Clean redundant argument to dict.get * Updated from global requirements * Fix Mutable default argument * gitignore .idea * Replace string format arguments with function parameters * Support unscoped token request * Don't use Mock.called\_once\_with that does not exist * Subnet Pool: Add "subnet pool show" command * Subnet Pool: Add "subnet pool list" command * Remove unused test-requirments * Subnet Pool: Add "subnet pool delete" command * Fix 'openstack --help' fails if clouds.yaml cannot be read * Floating IP: Neutron support for "ip floating list" command * Floating IP: Neutron support for "ip floating delete" command * Updated from global requirements * Updated from global requirements * Refactor security group rule delete to use SDK * Add "token revoke" for identity v3 * Fix DisplayCommandBase comments for cliff Lister subclass tests * Updated from global requirements * Add support for triggering an crash dump * Allow custom log levels for other loggers * Use assert\_not\_called() in common tests * Fix a spell typos * Refactor security group delete to use SDK * Add "os port show" command * Drop log\_method decorator * Updated from global requirements * log take\_action parameters in a single place * Update translation setup * Allow wait\_for\_delete to work for all clients * Updated from global requirements * Remove the Tuskar client * Updated from global requirements * Subnet List * Updated from global requirements * Updated from global requirements * log\_method: get logger from decorated method if unspecified * Set up logger of each command by metaclass * Add support to delete the ports * Updated from global requirements * Refactor "os availability zone list" * Changed the abstract columns and datalists from test cases of common and Identity * Updated from global requirements * Use assertTrue/False instead of assertEqual(T/F) * Updated from global requirements * Delete the unused LOG configure code * Refactor network endpoint enablement checking * Implementation for project unset cmd for python-openstackclient * Trivial: Remove useless string\_to\_bool() * Refactor: Initialize parser in setUp() in TestNonNegativeAction * Refactor: Initialize parser in setUp() in TestKeyValueAction * Replace assertEqual(None, \*) with assertIsNone(\*) * Improve output for "os security group show" * Add all regions to cloud configuration * Updated from global requirements * Router: Add "router show" command using SDK * Router: Add "router set" command using SDK * Updated from global requirements * Router: Add "router delete" command using SDK * Router: Add "router create" command using SDK * Updated from global requirements * Deprecated tox -downloadcache option removed * Router: Add "router list" command using SDK * Remove python-neutronclient requirement * The format\_exc method does not take an exception * Updated from global requirements * SDK integration: Add a temporary method to create network client using sdk * Updated from global requirements * Add reno for release notes management * Switch to ksa Session * autodocument commands from plugins using stevedore.sphinxext * Updated from global requirements * Change the home-page value in setup.cfg * Add "openstack server unshelve" into OSC * Trivial: Fix a typo * Move FakeServer to tests.common.v2.compute.fakes * Trivial: Add missing doc for parameter in wait\_for\_delete() * Remove py26 support * Add "openstack server shelve" into OSC * Trivial: Fix wrong doc for wait\_for\_status() * Updated from global requirements * Trivial: Remove doc for non-existing param in format\_dict() * Introduce random server faking mechanism * Enable FakeResource to fake methods * Allow error status to be specified * Remove deprecated 'project usage list' command * Remove LICENSE APPENDIX * validate non-ascii values for swift properties * Fix the bug of "openstack console log show" * Add "server stop" command to osc * Add "server start" command to osc * Allow int version numbers in the clouds.yaml * Rename context.py to logs.py * Allow debug to be set in configuration file * Updated from global requirements * Fix issue when displaying image\_member * Updated from global requirements * Add compute service delete * Updated from global requirements * Move session and fixtures to keystoneauth1 * Remove cliff-tablib from requirements.txt * Updated from global requirements * Updated from global requirements * Mask the sensitive values in debug log * Updated from global requirements * add set/unset support for objects in object store * add support for set/unset of container properties * Updated from global requirements * Rename swift account commands * Add support for showing account details * Add support for updating swift account properties * Add tests for find\_resource() * attempt to find resource by listing * Additional exception handling for find\_resource * Add image create support for image v2 * Change ignore-errors to ignore\_errors * Move option logging back to start if initialize\_app() * Set default auth plugin back to 'password' * Updated from global requirements * Updated from global requirements * Fix compute API version snafu * Use a common decorator to log 'take\_action' activation * Fix 'auhentication' spelling error/mistake * Create log configuration class * Override the debug default and help text * Move options to log level out of shell.py * Move set warnings filters to logging module * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Remove non-existing hacking deviations from doc * Set up every time record log in file * Alphabetize setup.cfg * Add set feature to volume type v2 * Add list feature to volume v2 * Updated from global requirements * Minor Documentation changes for code samples * Updated from global requirements * Add support for volume v2 commands * Add configuration show command * Add plugin interface version * Updated from global requirements * Add create and list for volume type v2 * add image member commands for image API * Fix the way auth\_type default value is overriden * Rename type.py to volume\_type.py * Add volume type show for volume v1 * More minor docs fixes * Do not set default versions in parsed args * Updated from global requirements * Remove requirements.txt from tox.ini * Rename endpoint type to interface * Updated from global requirements * temporarily skip help tests * Drop py33 support for Liberty * Fix interactive password prompt * Updated from global requirements * Fix wrong mock method call * add functional tests for identity v3 * Add --os-endpoint-type cli optional argument * Updated from global requirements * Add support for volume API v2 QoS commands * Alphabetize QoS specs * Add support for volume API v1 QoS commands * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Improve the hint message * Fix the typo in \`openstackclient/shell.py\` * Skip trying to set project\_domain\_id if not using password * Updated from global requirements * Updated from global requirements * Enables retrieval of project's parents and subtree * Add support for volume backup v2 command * Updated from global requirements * Add support for volume snapshot v2 command * Allow --insecure to override --os-cacert * Add EC2 support for identity v3 API * Get rid of oslo\_i18n deprecation notice * Rework shell tests * Ignore cover directory from git * Set tenant options on parsed namespace * Add support for volume v2 API * Add --wait to server delete * Use ostestr for test runs * Add cli tests for --verify and friends * Fix shell tests * Add support for v2 image set command * Remove oslo serialization requirement * Fix insecure/verify options * Use format options for functional tests * Fix functional test gate * Updated from global requirements * Send the correct user-agent to Keystone * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add os-client-config cli tests * minor syntax error in tox.ini * Minor logging/debug cleanups * Raise exception if no session is created * Functional tests run in many environments * Remove references to venv * Add --os-cloud support * Print warning on authentication error * Uncap library requirements for liberty * Defer client imports * Federation Service Providers CRUD operations * Add warning message if unknown version supplied * Fix session timing * Suppress warnings user can't fix * Updated from global requirements * Add the ability to set and unset flavor properties * Use cliff deferred help instead of homemade one * Move OSC auth plugins so they can be found * Add identity v3 catalog show * Add identity v3 catalog list * Add ability for diplaying hypervisor statistics * Raise AttributeError for unknown attributes * Updated from global requirements * Fix auth-required for help command * change oslo namespace to avoid warning * Updated from global requirements * Fix error msg in sort\_items * Implement trust in identity v3 api * Check volume status before extending size * Adding default user\_domain\_id parameter only when using password auth * Add sort support to image list * Updated from global requirements * Change test order to avoid incompatibliity * Add filter to image list * fix the wrong order of assertEqual args * Remove ignore portion of tox.ini * Begin low-level API for Image v1 and v2 * Add missing oslo-config to requirements * Default user domain id and project domain id * Add helpful messages when authN'ing with password * Add version url config workaround * Deprecate project usage list command * Updated from global requirements * Upgrade hacking to 0.10 * Updated from global requirements * Fix up snapshot command * Rename \`os project usage list\` to \`os usage list\` * Add usage show command * Don't import form keystoneclient.openstack.common * list availability zones for compute * Updated from global requirements * Updated from global requirements * Enhance the theming for modules page * add keystone v3 region object * Updated from global requirements * Add environment variable in the os-auth-type help * Liberalize version matching a bit * Tests work fine with random PYTHONHASHSEED * Updated from global requirements * Add --or-show option to user create * Add cliff-tablib to requirements * Use fixtures from keystoneclient for static data * Unscoped federated user-specific commands * Adjust some logging levels * Change --os-auth-plugin to --os-auth-type * Include support for using oslo debugger in tests * Clean up shell authentication * only generate one clientmanager instance in interactive mode * Remove ClientManager.\_service\_catalog * Remove now-unnecessary client creation hacks * use jsonutils in oslo.serialization instead of keystoneclient * Close files on server create, add tests * Move plugin stuff to clientmanager * Put pbr and six first in requirements list * Add plugin to support token-endpoint auth * Updated from global requirements * Fix operation on clouds with availability-zones * Add translation markers for user v2 actions * Add domain parameters to user show for Identity V3 * Support for keystone auth plugins * Add 'command list' command * CRUD operations for federated protocols * Update for cliff commandmanager >=1.6.1 * Implement CRUD operations for Mapping objects * Update gitignore * Place the command to generate docs on one line * Remove duplicate env function in shell.py * Add functional tests to osc * Add low-level API base class * Test top-to-bottom: object-store containers * Updated from global requirements * utils.find\_resource does not catch right exception * Remove unused reference to keyring * Use oslo.utils * Updated from global requirements * Add service catalog commands * Add preliminary save container support * Add preliminary support for downloading objects * Updated from global requirements * Use Keystone client session.Session * Add action 'user password set' for identiy v3 * Unordered dicts and lists causes variable results * Leverage openstack.common.importutils for import\_class * Work toward Python 3.4 support and testing * Make Identity client load like the others * Change app.restapi to app.client\_manager.session * Add i18n module to openstackclient * Add oslo.i18n as a dependency * Updated from global requirements * Add commands for object upload and delete * Use oslosphinx to generate documentation * Updated from global requirements * Add container create and delete support * Fix PEP8 E302 errors * Add Python 3 support * Fix server resize * Add basic timing support * Move network stuff to v2 instead of v2\_0 * Catch SystemExit for parse args * Python 3: remove a useless code to safe\_encode() * Remove keyring support from openstackclient * trust authentication * Updated from global requirements * Sort/clean setup.cfg * Rename token classes to match command * Fix PEP8 E126 and E202 errors * Fix PEP8 E265 errors * Fix PEP8 H405 errors * Network CRUD * Updated from global requirements * Updated from global requirements * Change the token verb to issue/revoke * Add a docs job to tox.ini * Fix find\_resource for keystone and cinder * Refactor oauth1 code for updates * Updated from global requirements * Clean up logging levels * Ignore most of the new hacking 0.9.2 rules * Add support for extension list * Add role assignments list support to identity v3 * Add token delete command for identity v2 * Fixed several typos throughout the codebase * replace string format arguments with function parameters * Updated from global requirements * Implement CRUD operations for Identity Providers * Updated from global requirements * move read\_blob\_file\_contents to utils * Produce a useful error message for NoUniqueMatch * Updated from global requirements * Make bash comple command best effort to authorize * Updated from global requirements * In anticipation of network agents, rename compute * Updated from global requirements * Add ability to prompt for passwords for user create and set * Fix some help strings * Use six.iteritems() rather than dict.iteritems() * Remove tox locale overrides * Add token create subcommand for identity v3 api * Updated from global requirements * Remove copyright from empty files * Add token create subcommand for identity v2 api * Sync with global requirements * Add support for specifying custom domains * Displaying curl commands for nova and cinder calls * Remove mox3 requirement * Updated from global requirements * Add missing requests and six requirements * Add module list command * Update OSC's CommandManager subclass * Bring RESTApi closer to ithe imminent keystoneclient.Session * Add return Closes-Bug: 1246356 * Restore Object API name 'object-store' * Expand support for command extensions * Fix typo * Support building wheels (PEP-427) * Add server image create command * Complete basic test infrastructure * change execute to run * Update URL for global hacking doc and fix typos * Remove httpretty from test requirements * Updated from global requirements * Updated from global requirements * Add options to support TLS certificate verification * Updated from global requirements * Add object-store show commands * Sort entrypoints in setup.cfg * Fix security group entrypoints * Delay authentication to handle commands that do not require it * Prepare for Identity v3 tests * Add to clientmanager tests * Add Identity v2 role and service tests * Refactor fake data for projects and users * Update tox.ini for new tox 1.6 config * Update requirements.txt and test-requirements.txt * Object API commands using our REST API layer * Create a new base REST API interface * Re-order oauth commands and sync with keystoneclient * Add Identity v2 project tests * Updated from global requirements * Sync with global requirements * Change version reporting to use pbr * Prep for 0.2 release (0.2.rc1) * Remove 'oauth authorization show' function from identity v3 * Remove tenant round 3 - other commands * Remove tenant round 2 - Identity API * Remove tenant round 1 - global options * Add server ssh command * Add security group commands * Add server resize command * Add server migrate command * Add server commands: (un)lock, (un)rescue, (un)set, add/remove volume * Add usage command for compute api * Clean up properties (metadata) formatting * Add aggregate commands * Complete Image v1 * Add quota commands * Add list and delete authorizations for oauth commands * Add show limits command * Remove api = apiName calls from each method * Add authenticate method to oauth code * Add EC2 credentials CRUD * Finish up v3 role commands * Add methods for user and group interactions * Move tests into project package * Add OAuth support for Identity V3 * Remove explicit distribute depend * Add volume backup commands * python3: Introduce py33 to tox.ini * Rename requires files to standard names * Fix identity v2.0 entry point * Tweak volume commands and add k=v argparse action * Migrate to pbr * Migrate to flake8 * Fix flake8 errors in anticipation of flake8 patch * Switch to noun-verb command forms * Add console commands * Adds image \`create\` and \`delete\` functionality * Add fixed-ip and floating-ip commands * Add compute keypair commands * Add policy to identity v3 * Add metadata support for volume * Make entry point strings readable * Add extra-specs support for volume-type * Add endpoint v3 functionality * Add service v3 support for identity * Add functionality for add-role commands * Add a simple extension hook * Add role v3 support to identity in openstack client * Added compute hypervisor support * Turn down requests logging level * Add snapshot support for v1 volume * add domain, credential to identity v3 api * Add volume support for openstack client * Add compute hosts support * Add metadata support for volume type * Added compute service support * Add quota v1 support for volume * Added compute flavor support * Added compute agent support * Correct the version mapping to image service * Add Cinder API V1 Support * Multiple API version support * Update .coveragerc * Upgraded to PEP8 1.3.3 to stay aligned with Nova, etc * Clean up test\_shell so that the tests are captured though the test framework * Use install\_venv\_common.py from oslo * v3 identity - group and project api * Sync latest openstack-common updates * Standardize on a copyright header and ensure all files have them * Migrate from nose to testr * Clean up test environment and remove unused imports * Updated gitignore and manifest * Adds Glance API v2 support * Fixes setup compatibility issue on Windows * Add OpenStack trove classifier for PyPI * Update compute client bits * Keyring support for openstackclient * If no password in env or command line, try prompting * Add read\_versioninfo method * Add post-tag versioning * Fix pep8 issues * Move docs to doc * minor fixes * Add role CRUD commands * Add endpoint CRUD commands * Clean up tenant and server * Update service commands * Add user CRUD commands * fix authentication setup in interactive mode and improve error handling so tracebacks are not printed twice * Revise command boolean flags * Move get\_client\_class() to common.utils * Add tenant CRUD commands * Add API versioning support * look at the command the user is going to run before trying to authenticate them * Add copyright notices and update dates * Add tenant commands, work on service * More identity client config * Remove printt * Add Identity to ClientManager * Fix "help" command and implement "list server" and "show server" * Change binary name to 'openstack' * Auto generate AUTHORS for python-openstackclient * Shell init & logging * Reset project version to 0.1 * Add openstack-common and test infrastructure * Cleanup auth client path * Add 'list service' command and common modules * Add token auth to shell and README * Begin to add Keystone auth * Change to argparse to match cliff 0.2 * Use cliff * Set up common utils * Add openstackclient bits * First commit osc-lib-1.9.0/tox.ini0000666000175100017510000000316513227377263014433 0ustar zuulzuul00000000000000[tox] minversion = 2.0 envlist = py35,py27,pep8 skipdist = True [testenv] usedevelop = True install_command = pip install {opts} {packages} setenv = VIRTUAL_ENV={envdir} OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = ostestr {posargs} whitelist_externals = ostestr [testenv:unit-tips] commands = pip install -q -e "git+file://{toxinidir}/../cliff#egg=cliff" pip install -q -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" pip install -q -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" pip install -q -e "git+file://{toxinidir}/../python-openstacksdk#egg=openstacksdk" pip freeze ostestr {posargs} whitelist_externals = ostestr [testenv:pep8] commands = flake8 [testenv:venv] commands = {posargs} [testenv:cover] commands = python setup.py test --coverage --coverage-package-name=osc_lib --testr-args='{posargs}' coverage report [testenv:debug] commands = oslo_debug_helper -t osc_lib/tests {posargs} [testenv:docs] commands = python setup.py build_sphinx [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] show-source = True exclude = .git,.tox,dist,doc,*lib/python*,*egg,build,tools # If 'ignore' is not set there are default errors and warnings that are set # Doc: http://flake8.readthedocs.org/en/latest/config.html#default ignore = __ osc-lib-1.9.0/osc_lib.egg-info/0000775000175100017510000000000013227377613016214 5ustar zuulzuul00000000000000osc-lib-1.9.0/osc_lib.egg-info/PKG-INFO0000664000175100017510000000726013227377612017315 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: osc-lib Version: 1.9.0 Summary: OpenStackClient Library Home-page: https://docs.openstack.org/osc-lib/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======= osc-lib ======= .. image:: https://img.shields.io/pypi/v/osc-lib.svg :target: https://pypi.python.org/pypi/osc-lib/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/osc-lib.svg :target: https://pypi.python.org/pypi/osc-lib/ :alt: Downloads OpenStackClient (aka OSC) is a command-line client for OpenStack. osc-lib is a package of common support modules for writing OSC plugins. * `PyPi`_ - package installation * `Online Documentation`_ * `Launchpad project`_ - part of OpenStackClient * `Bugs`_ - issue tracking * `Source`_ * `Developer` - getting started as a developer * `Contributing` - contributing code * `Testing` - testing code * IRC: #openstack-sdks on Freenode (irc.freenode.net) * License: Apache 2.0 .. _PyPi: https://pypi.python.org/pypi/osc-lib .. _Online Documentation: http://docs.openstack.org/osc-lib/latest/ .. _Launchpad project: https://launchpad.net/python-openstackclient .. _Bugs: https://bugs.launchpad.net/python-openstackclient .. _Source: https://git.openstack.org/cgit/openstack/osc-lib .. _Developer: http://docs.openstack.org/project-team-guide/project-setup/python.html .. _Contributing: http://docs.openstack.org/infra/manual/developers.html .. _Testing: http://docs.openstack.org/osc-lib/latest/contributor/#testing Getting Started =============== osc-lib can be installed from PyPI using pip:: pip install osc-lib Transition From OpenStackclient =============================== This library was extracted from the main OSC repo after the OSC 2.4.0 release. The following are the changes to imports that will cover the majority of transition to using osc-lib: * openstackclient.api.api -> osc_lib.api.api * openstackclient.api.auth -> osc_lib.api.auth * openstackclient.api.utils -> osc_lib.api.utils * openstackclient.common.command -> osc_lib.command.command * openstackclient.common.commandmanager -> osc_lib.command.commandmanager * openstackclient.common.exceptions -> osc_lib.exceptions * openstackclient.common.logs -> osc_lib.logs * openstackclient.common.parseractions -> osc_lib.cli.parseractions * openstackclient.common.session -> osc_lib.session * openstackclient.common.utils -> osc_lib.utils * openstackclient.i18n -> osc_lib.i18n * openstackclient.shell -> osc_lib.shell Also, some of the test fixtures and modules may be used: * openstackclient.tests.fakes -> osc_lib.tests.fakes * openstackclient.tests.utils -> osc_lib.tests.utils Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 osc-lib-1.9.0/osc_lib.egg-info/top_level.txt0000664000175100017510000000001013227377612020734 0ustar zuulzuul00000000000000osc_lib osc-lib-1.9.0/osc_lib.egg-info/not-zip-safe0000664000175100017510000000000113227377575020451 0ustar zuulzuul00000000000000 osc-lib-1.9.0/osc_lib.egg-info/pbr.json0000664000175100017510000000005613227377612017672 0ustar zuulzuul00000000000000{"git_version": "2811832", "is_release": true}osc-lib-1.9.0/osc_lib.egg-info/dependency_links.txt0000664000175100017510000000000113227377612022261 0ustar zuulzuul00000000000000 osc-lib-1.9.0/osc_lib.egg-info/SOURCES.txt0000664000175100017510000000507113227377613020103 0ustar zuulzuul00000000000000.coveragerc .mailmap .stestr.conf .testr.conf .zuul.yaml AUTHORS ChangeLog HACKING.rst LICENSE README.rst requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/Makefile doc/ext/__init__.py doc/ext/apidoc.py doc/source/conf.py doc/source/index.rst doc/source/contributor/index.rst doc/source/reference/index.rst doc/source/user/change_log.rst doc/source/user/index.rst doc/source/user/transition.rst osc_lib/__init__.py osc_lib/clientmanager.py osc_lib/exceptions.py osc_lib/i18n.py osc_lib/logs.py osc_lib/session.py osc_lib/shell.py osc_lib/version.py osc_lib.egg-info/PKG-INFO osc_lib.egg-info/SOURCES.txt osc_lib.egg-info/dependency_links.txt osc_lib.egg-info/not-zip-safe osc_lib.egg-info/pbr.json osc_lib.egg-info/requires.txt osc_lib.egg-info/top_level.txt osc_lib/api/__init__.py osc_lib/api/api.py osc_lib/api/auth.py osc_lib/api/utils.py osc_lib/cli/__init__.py osc_lib/cli/client_config.py osc_lib/cli/format_columns.py osc_lib/cli/parseractions.py osc_lib/command/__init__.py osc_lib/command/command.py osc_lib/command/commandmanager.py osc_lib/command/timing.py osc_lib/tests/__init__.py osc_lib/tests/fakes.py osc_lib/tests/test_clientmanager.py osc_lib/tests/test_logs.py osc_lib/tests/test_shell.py osc_lib/tests/api/__init__.py osc_lib/tests/api/fakes.py osc_lib/tests/api/test_api.py osc_lib/tests/api/test_utils.py osc_lib/tests/cli/__init__.py osc_lib/tests/cli/test_client_config.py osc_lib/tests/cli/test_format_columns.py osc_lib/tests/cli/test_parseractions.py osc_lib/tests/command/__init__.py osc_lib/tests/command/test_command.py osc_lib/tests/command/test_commandmanager.py osc_lib/tests/command/test_timing.py osc_lib/tests/utils/__init__.py osc_lib/tests/utils/test_columns.py osc_lib/tests/utils/test_utils.py osc_lib/utils/__init__.py osc_lib/utils/columns.py releasenotes/notes/.placeholder releasenotes/notes/1.0-summary-47dcce446d6a512b.yaml releasenotes/notes/add-MultiKeyValueCommaAction-class-01dd254a287d70d2.yaml releasenotes/notes/arg-precedence-1ba9fd6929650830.yaml releasenotes/notes/bug-1558690-1528b637f2c0a449.yaml releasenotes/notes/bug-1630822-mask-password-on-debug-20dcdf1c54e84fa1.yaml releasenotes/notes/keystone-to-keystone-9b2e55b051775322.yaml releasenotes/notes/os-profile-as-environment-variable-a5e232e9ca7c5171.yaml releasenotes/notes/shell-argv-decode-cdc13dc0c4ec07af.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholderosc-lib-1.9.0/osc_lib.egg-info/requires.txt0000664000175100017510000000032513227377612020613 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 six>=1.10.0 Babel!=2.4.0,>=2.3.4 cliff!=2.9.0,>=2.8.0 keystoneauth1>=3.3.0 openstacksdk>=0.9.19 os-client-config>=1.28.0 oslo.i18n>=3.15.3 oslo.utils>=3.33.0 simplejson>=3.5.1 stevedore>=1.20.0