Пример #1
0
def _get_client(service_key):
    class_mapping = constructors.get_constructor_mapping()
    if service_key not in class_mapping:
        raise exceptions.OpenStackConfigException(
            "Service {service_key} is unkown. Please pass in a client"
            " constructor or submit a patch to os-client-config".format(
                service_key=service_key))
    mod_name, ctr_name = class_mapping[service_key].rsplit('.', 1)
    lib_name = mod_name.split('.')[0]
    try:
        mod = importlib.import_module(mod_name)
    except ImportError:
        raise exceptions.OpenStackConfigException(
            "Client for '{service_key}' was requested, but"
            " {mod_name} was unable to be imported. Either import"
            " the module yourself and pass the constructor in as an argument,"
            " or perhaps you do not have python-{lib_name} installed.".format(
                service_key=service_key, mod_name=mod_name, lib_name=lib_name))
    try:
        ctr = getattr(mod, ctr_name)
    except AttributeError:
        raise exceptions.OpenStackConfigException(
            "Client for '{service_key}' was requested, but although"
            " {mod_name} imported fine, the constructor at {fullname}"
            " as not found. Please check your installation, we have no"
            " clue what is wrong with your computer.".format(
                service_key=service_key,
                mod_name=mod_name,
                fullname=class_mapping[service_key]))
    return ctr
Пример #2
0
    def _fix_backwards_networks(self, cloud):
        # Leave the external_network and internal_network keys in the
        # dict because consuming code might be expecting them.
        networks = []
        # Normalize existing network entries
        for net in cloud.get('networks', []):
            name = net.get('name')
            if not name:
                raise exceptions.OpenStackConfigException(
                    'Entry in network list is missing required field "name".')
            network = dict(
                name=name,
                routes_externally=get_boolean(net.get('routes_externally')),
                nat_source=get_boolean(net.get('nat_source')),
                nat_destination=get_boolean(net.get('nat_destination')),
                default_interface=get_boolean(net.get('default_interface')),
            )
            # routes_ipv4_externally defaults to the value of routes_externally
            network['routes_ipv4_externally'] = get_boolean(
                net.get(
                    'routes_ipv4_externally', network['routes_externally']))
            # routes_ipv6_externally defaults to the value of routes_externally
            network['routes_ipv6_externally'] = get_boolean(
                net.get(
                    'routes_ipv6_externally', network['routes_externally']))
            networks.append(network)

        for key in ('external_network', 'internal_network'):
            external = key.startswith('external')
            if key in cloud and 'networks' in cloud:
                raise exceptions.OpenStackConfigException(
                    "Both {key} and networks were specified in the config."
                    " Please remove {key} from the config and use the network"
                    " list to configure network behavior.".format(key=key))
            if key in cloud:
                warnings.warn(
                    "{key} is deprecated. Please replace with an entry in"
                    " a dict inside of the networks list with name: {name}"
                    " and routes_externally: {external}".format(
                        key=key, name=cloud[key], external=external))
                networks.append(dict(
                    name=cloud[key],
                    routes_externally=external,
                    nat_destination=not external,
                    default_interface=external))

        # Validate that we don't have duplicates
        self._validate_networks(networks, 'nat_destination')
        self._validate_networks(networks, 'default_interface')

        cloud['networks'] = networks
        return cloud
Пример #3
0
 def _expand_vendor_profile(self, name, cloud, our_cloud):
     # Expand a profile if it exists. 'cloud' is an old confusing name
     # for this.
     profile_name = our_cloud.get('profile', our_cloud.get('cloud', None))
     if profile_name and profile_name != self.envvar_key:
         if 'cloud' in our_cloud:
             warnings.warn(
                 "{0} use the keyword 'cloud' to reference a known "
                 "vendor profile. This has been deprecated in favor of the "
                 "'profile' keyword.".format(self.config_filename))
         vendor_filename, vendor_file = self._load_vendor_file()
         if vendor_file and profile_name in vendor_file['public-clouds']:
             _auth_update(cloud, vendor_file['public-clouds'][profile_name])
         else:
             profile_data = vendors.get_profile(profile_name)
             if profile_data:
                 status = profile_data.pop('status', 'active')
                 message = profile_data.pop('message', '')
                 if status == 'deprecated':
                     warnings.warn(
                         "{profile_name} is deprecated: {message}".format(
                             profile_name=profile_name, message=message))
                 elif status == 'shutdown':
                     raise exceptions.OpenStackConfigException(
                         "{profile_name} references a cloud that no longer"
                         " exists: {message}".format(
                             profile_name=profile_name, message=message))
                 _auth_update(cloud, profile_data)
             else:
                 # Can't find the requested vendor config, go about business
                 warnings.warn("Couldn't find the vendor profile '{0}', for"
                               " the cloud '{1}'".format(
                                   profile_name, name))
Пример #4
0
def _fix_argv(argv):
    # Transform any _ characters in arg names to - so that we don't
    # have to throw billions of compat argparse arguments around all
    # over the place.
    processed = collections.defaultdict(list)
    for index in range(0, len(argv)):
        # If the value starts with '--' and has '-' or '_' in it, then
        # it's worth looking at it
        if re.match('^--.*(_|-)+.*', argv[index]):
            split_args = argv[index].split('=')
            orig = split_args[0]
            new = orig.replace('_', '-')
            if orig != new:
                split_args[0] = new
                argv[index] = "=".join(split_args)
            # Save both for later so we can throw an error about dupes
            processed[new].append(orig)
    overlap = []
    for new, old in processed.items():
        if len(old) > 1:
            overlap.extend(old)
    if overlap:
        raise exceptions.OpenStackConfigException(
            "The following options were given: '{options}' which contain"
            " duplicates except that one has _ and one has -. There is"
            " no sane way for us to know what you're doing. Remove the"
            " duplicate option and try again".format(
                options=','.join(overlap)))
Пример #5
0
 def get_session(self):
     """Return a keystoneauth session based on the auth credentials."""
     if self._keystone_session is None:
         if not self._auth:
             raise exceptions.OpenStackConfigException(
                 "Problem with auth parameters")
         (verify, cert) = self.get_requests_verify_args()
         # Turn off urllib3 warnings about insecure certs if we have
         # explicitly configured requests to tell it we do not want
         # cert verification
         if not verify:
             self.log.debug("Turning off SSL warnings for {cloud}:{region}"
                            " since verify=False".format(
                                cloud=self.name, region=self.region))
         requestsexceptions.squelch_warnings(insecure_requests=not verify)
         self._keystone_session = self._session_constructor(
             auth=self._auth,
             verify=verify,
             cert=cert,
             timeout=self.config['api_timeout'])
         if hasattr(self._keystone_session, 'additional_user_agent'):
             self._keystone_session.additional_user_agent.append(
                 ('os-client-config', os_client_config.__version__))
         # Using old keystoneauth with new os-client-config fails if
         # we pass in app_name and app_version. Those are not essential,
         # nor a reason to bump our minimum, so just test for the session
         # having the attribute post creation and set them then.
         if hasattr(self._keystone_session, 'app_name'):
             self._keystone_session.app_name = self._app_name
         if hasattr(self._keystone_session, 'app_version'):
             self._keystone_session.app_version = self._app_version
     return self._keystone_session
Пример #6
0
    def get_one_cloud(self, name='openstack', region=None):

        if not region:
            region = self._get_region(name)

        config = self._get_base_cloud_config(name)
        config['region_name'] = region

        for key in BOOL_KEYS:
            if key in config:
                config[key] = get_boolean(config[key])

        for key in REQUIRED_VALUES:
            if key not in config or not config[key]:
                raise exceptions.OpenStackConfigException(
                    'Unable to find full auth information for cloud {name} in'
                    ' config files {files}'
                    ' or environment variables.'.format(
                        name=name, files=','.join(self._config_files)))

        # If any of the defaults reference other values, we need to expand
        for (key, value) in config.items():
            if hasattr(value, 'format'):
                config[key] = value.format(**config)

        return cloud_config.CloudConfig(name=name,
                                        region=region,
                                        config=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
Пример #8
0
 def get_session(self):
     """Return a keystoneauth session based on the auth credentials."""
     if self._keystone_session is None:
         if not self._auth:
             raise exceptions.OpenStackConfigException(
                 "Problem with auth parameters")
         (verify, cert) = self.get_requests_verify_args()
         # Turn off urllib3 warnings about insecure certs if we have
         # explicitly configured requests to tell it we do not want
         # cert verification
         if not verify:
             self.log.debug("Turning off SSL warnings for {cloud}:{region}"
                            " since verify=False".format(
                                cloud=self.name, region=self.region))
         requestsexceptions.squelch_warnings(insecure_requests=not verify)
         self._keystone_session = self._session_constructor(
             auth=self._auth,
             app_name=self._app_name,
             app_version=self._app_version,
             verify=verify,
             cert=cert,
             timeout=self.config['api_timeout'])
         if hasattr(self._keystone_session, 'additional_user_agent'):
             self._keystone_session.additional_user_agent.append(
                 ('os-client-config', os_client_config.__version__))
     return self._keystone_session
Пример #9
0
 def _validate_networks(self, networks, key):
     value = None
     for net in networks:
         if value and net[key]:
             raise exceptions.OpenStackConfigException(
                 "Duplicate network entries for {key}: {net1} and {net2}."
                 " Only one network can be flagged with {key}".format(
                     key=key, net1=value['name'], net2=net['name']))
         if not value and net[key]:
             value = net
Пример #10
0
 def test__raise_exception_on_no_cloud(self, mock_cloud, mock_config):
     """
     Test that when os-client-config can't find a named cloud, a
     shade exception is emitted.
     """
     mock_config.return_value.get_one_cloud.side_effect = (
         occ_exc.OpenStackConfigException())
     self.assertRaises(exc.OpenStackCloudException,
                       inventory.OpenStackInventory,
                       cloud='supercloud')
     mock_config.return_value.get_one_cloud.assert_called_once_with(
         'supercloud')
Пример #11
0
    def _validate_auth_ksc(self, config):
        try:
            import keystoneclient.auth as ksc_auth
        except ImportError:
            return config

        # May throw a keystoneclient.exceptions.NoMatchingPlugin
        plugin_options = ksc_auth.get_plugin_class(
            config['auth_type']).get_options()

        for p_opt in plugin_options:
            # if it's in config.auth, win, kill it from config dict
            # if it's in config and not in config.auth, move it
            # deprecated loses to current
            # provided beats default, deprecated or not
            winning_value = self._find_winning_auth_value(
                p_opt, config['auth'])
            if not winning_value:
                winning_value = self._find_winning_auth_value(p_opt, config)

            # 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:
                raise exceptions.OpenStackConfigException(
                    'Unable to find auth information for cloud'
                    ' {cloud} in config files {files}'
                    ' or environment variables. Missing value {auth_key}'
                    ' required for auth plugin {plugin}'.format(
                        cloud=cloud,
                        files=','.join(self._config_files),
                        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_opts]:
                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

        return config
Пример #12
0
 def get_session(self):
     """Return a keystoneauth session based on the auth credentials."""
     if self._keystone_session is None:
         if not self._auth:
             raise exceptions.OpenStackConfigException(
                 "Problem with auth parameters")
         (verify, cert) = self.get_requests_verify_args()
         # Turn off urllib3 warnings about insecure certs if we have
         # explicitly configured requests to tell it we do not want
         # cert verification
         if not verify:
             self.log.debug("Turning off SSL warnings for {cloud}:{region}"
                            " since verify=False".format(
                                cloud=self.name, region=self.region))
         requestsexceptions.squelch_warnings(insecure_requests=not verify)
         self._keystone_session = session.Session(
             auth=self._auth,
             verify=verify,
             cert=cert,
             timeout=self.config['api_timeout'])
     return self._keystone_session
Пример #13
0
    def _get_base_cloud_config(self, name):
        cloud = dict()

        # Only validate cloud name if one was given
        if name and name not in self.cloud_config['clouds']:
            raise exceptions.OpenStackConfigException(
                "Cloud {name} was not found.".format(name=name))

        our_cloud = self.cloud_config['clouds'].get(name, dict())

        # Get the defaults
        cloud.update(self.defaults)
        self._expand_vendor_profile(name, cloud, our_cloud)

        if 'auth' not in cloud:
            cloud['auth'] = dict()

        _auth_update(cloud, our_cloud)
        if 'cloud' in cloud:
            del cloud['cloud']

        return cloud
Пример #14
0
    def _get_region(self, cloud=None, region_name=''):
        if region_name is None:
            region_name = ''
        if not cloud:
            return self._expand_region_name(region_name)

        regions = self._get_known_regions(cloud)
        if not regions:
            return self._expand_region_name(region_name)

        if not region_name:
            return regions[0]

        for region in regions:
            if region['name'] == region_name:
                return region

        raise exceptions.OpenStackConfigException(
            'Region {region_name} is not a valid region name for cloud'
            ' {cloud}. Valid choices are {region_list}. Please note that'
            ' region names are case sensitive.'.format(
                region_name=region_name,
                region_list=','.join([r['name'] for r in regions]),
                cloud=cloud))
Пример #15
0
    def register_argparse_arguments(self, parser, argv, service_keys=None):
        """Register all of the common argparse options needed.

        Given an argparse parser, register the keystoneauth Session arguments,
        the keystoneauth Auth Plugin Options and os-cloud. Also, peek in the
        argv to see if all of the auth plugin options should be registered
        or merely the ones already configured.
        :param argparse.ArgumentParser: parser to attach argparse options to
        :param list argv: the arguments provided to the application
        :param string service_keys: Service or list of services this argparse
                                    should be specialized for, if known.
                                    The first item in the list will be used
                                    as the default value for service_type
                                    (optional)

        :raises exceptions.OpenStackConfigException if an invalid auth-type
                                                    is requested
        """

        if service_keys is None:
            service_keys = []

        # Fix argv in place - mapping any keys with embedded _ in them to -
        _fix_argv(argv)

        local_parser = argparse_mod.ArgumentParser(add_help=False)

        for p in (parser, local_parser):
            p.add_argument('--os-cloud',
                           metavar='<name>',
                           default=os.environ.get('OS_CLOUD', None),
                           help='Named cloud to connect to')

        # we need to peek to see if timeout was actually passed, since
        # the keystoneauth declaration of it has a default, which means
        # we have no clue if the value we get is from the ksa default
        # for from the user passing it explicitly. We'll stash it for later
        local_parser.add_argument('--timeout', metavar='<timeout>')

        # We need for get_one_cloud to be able to peek at whether a token
        # was passed so that we can swap the default from password to
        # token if it was. And we need to also peek for --os-auth-token
        # for novaclient backwards compat
        local_parser.add_argument('--os-token')
        local_parser.add_argument('--os-auth-token')

        # Peek into the future and see if we have an auth-type set in
        # config AND a cloud set, so that we know which command line
        # arguments to register and show to the user (the user may want
        # to say something like:
        #   openstack --os-cloud=foo --os-oidctoken=bar
        # although I think that user is the cause of my personal pain
        options, _args = local_parser.parse_known_args(argv)
        if options.timeout:
            self._argv_timeout = True

        # validate = False because we're not _actually_ loading here
        # we're only peeking, so it's the wrong time to assert that
        # the rest of the arguments given are invalid for the plugin
        # chosen (for instance, --help may be requested, so that the
        # user can see what options he may want to give
        cloud = self.get_one_cloud(argparse=options, validate=False)
        default_auth_type = cloud.config['auth_type']

        try:
            loading.register_auth_argparse_arguments(parser,
                                                     argv,
                                                     default=default_auth_type)
        except Exception:
            # Hidiing the keystoneauth exception because we're not actually
            # loading the auth plugin at this point, so the error message
            # from it doesn't actually make sense to os-client-config users
            options, _args = parser.parse_known_args(argv)
            plugin_names = loading.get_available_plugin_names()
            raise exceptions.OpenStackConfigException(
                "An invalid auth-type was specified: {auth_type}."
                " Valid choices are: {plugin_names}.".format(
                    auth_type=options.os_auth_type,
                    plugin_names=",".join(plugin_names)))

        if service_keys:
            primary_service = service_keys[0]
        else:
            primary_service = None
        loading.register_session_argparse_arguments(parser)
        adapter.register_adapter_argparse_arguments(
            parser, service_type=primary_service)
        for service_key in service_keys:
            # legacy clients have un-prefixed api-version options
            parser.add_argument('--{service_key}-api-version'.format(
                service_key=service_key.replace('_', '-'),
                help=argparse_mod.SUPPRESS))
            adapter.register_service_adapter_argparse_arguments(
                parser, service_type=service_key)

        # Backwards compat options for legacy clients
        parser.add_argument('--http-timeout', help=argparse_mod.SUPPRESS)
        parser.add_argument('--os-endpoint-type', help=argparse_mod.SUPPRESS)
        parser.add_argument('--endpoint-type', help=argparse_mod.SUPPRESS)
Пример #16
0
    def __init__(self,
                 config_files=None,
                 vendor_files=None,
                 override_defaults=None,
                 force_ipv4=None,
                 envvar_prefix=None,
                 secure_files=None,
                 pw_func=None,
                 session_constructor=None,
                 app_name=None,
                 app_version=None,
                 load_yaml_config=True):
        self.log = _log.setup_logging(__name__)
        self._session_constructor = session_constructor
        self._app_name = app_name
        self._app_version = app_version

        if load_yaml_config:
            self._config_files = config_files or CONFIG_FILES
            self._secure_files = secure_files or SECURE_FILES
            self._vendor_files = vendor_files or VENDOR_FILES
        else:
            self._config_files = []
            self._secure_files = []
            self._vendor_files = []

        config_file_override = os.environ.pop('OS_CLIENT_CONFIG_FILE', None)
        if config_file_override:
            self._config_files.insert(0, config_file_override)

        secure_file_override = os.environ.pop('OS_CLIENT_SECURE_FILE', None)
        if secure_file_override:
            self._secure_files.insert(0, secure_file_override)

        self.defaults = defaults.get_defaults()
        if override_defaults:
            self.defaults.update(override_defaults)

        # First, use a config file if it exists where expected
        self.config_filename, self.cloud_config = self._load_config_file()
        _, secure_config = self._load_secure_file()
        if secure_config:
            self.cloud_config = _merge_clouds(self.cloud_config, secure_config)

        if not self.cloud_config:
            self.cloud_config = {'clouds': {}}
        if 'clouds' not in self.cloud_config:
            self.cloud_config['clouds'] = {}

        # Grab ipv6 preference settings from env
        client_config = self.cloud_config.get('client', {})

        if force_ipv4 is not None:
            # If it's passed in to the constructor, honor it.
            self.force_ipv4 = force_ipv4
        else:
            # Get the backwards compat value
            prefer_ipv6 = get_boolean(
                os.environ.pop(
                    'OS_PREFER_IPV6',
                    client_config.get('prefer_ipv6',
                                      client_config.get('prefer-ipv6', True))))
            force_ipv4 = get_boolean(
                os.environ.pop(
                    'OS_FORCE_IPV4',
                    client_config.get('force_ipv4',
                                      client_config.get('broken-ipv6',
                                                        False))))

            self.force_ipv4 = force_ipv4
            if not prefer_ipv6:
                # this will only be false if someone set it explicitly
                # honor their wishes
                self.force_ipv4 = True

        # Next, process environment variables and add them to the mix
        self.envvar_key = os.environ.pop('OS_CLOUD_NAME', 'envvars')
        if self.envvar_key in self.cloud_config['clouds']:
            raise exceptions.OpenStackConfigException(
                '"{0}" defines a cloud named "{1}", but'
                ' OS_CLOUD_NAME is also set to "{1}". Please rename'
                ' either your environment based cloud, or one of your'
                ' file-based clouds.'.format(self.config_filename,
                                             self.envvar_key))
        # Pull out OS_CLOUD so that if it's the only thing set, do not
        # make an envvars cloud
        self.default_cloud = os.environ.pop('OS_CLOUD', None)

        envvars = _get_os_environ(envvar_prefix=envvar_prefix)
        if envvars:
            self.cloud_config['clouds'][self.envvar_key] = envvars
            if not self.default_cloud:
                self.default_cloud = self.envvar_key

        # Finally, fall through and make a cloud that starts with defaults
        # because we need somewhere to put arguments, and there are neither
        # config files or env vars
        if not self.cloud_config['clouds']:
            self.cloud_config = dict(clouds=dict(defaults=dict(self.defaults)))
            self.default_cloud = 'defaults'

        self._cache_expiration_time = 0
        self._cache_path = CACHE_PATH
        self._cache_class = 'dogpile.cache.null'
        self._cache_arguments = {}
        self._cache_expiration = {}
        if 'cache' in self.cloud_config:
            cache_settings = self._normalize_keys(self.cloud_config['cache'])

            # expiration_time used to be 'max_age' but the dogpile setting
            # is expiration_time. Support max_age for backwards compat.
            self._cache_expiration_time = cache_settings.get(
                'expiration_time',
                cache_settings.get('max_age', self._cache_expiration_time))

            # If cache class is given, use that. If not, but if cache time
            # is given, default to memory. Otherwise, default to nothing.
            # to memory.
            if self._cache_expiration_time:
                self._cache_class = 'dogpile.cache.memory'
            self._cache_class = self.cloud_config['cache'].get(
                'class', self._cache_class)

            self._cache_path = os.path.expanduser(
                cache_settings.get('path', self._cache_path))
            self._cache_arguments = cache_settings.get('arguments',
                                                       self._cache_arguments)
            self._cache_expiration = cache_settings.get(
                'expiration', self._cache_expiration)

        # Flag location to hold the peeked value of an argparse timeout value
        self._argv_timeout = False

        # Save the password callback
        # password = self._pw_callback(prompt="Password: ")
        self._pw_callback = pw_func