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.ConfigException( '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.ConfigException( "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
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.ConfigException( "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 {full_name}" " since verify=False".format(full_name=self.full_name)) requestsexceptions.squelch_warnings(insecure_requests=not verify) self._keystone_session = self._session_constructor( auth=self._auth, verify=verify, cert=cert, timeout=self.config.get('api_timeout'), discovery_cache=self._discovery_cache) self.insert_user_agent() # 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
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.ConfigException( "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)))
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.ConfigException( "{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))
def _get_base_cloud_config(self, name, profile=None): cloud = dict() # Only validate cloud name if one was given if name and name not in self.cloud_config['clouds']: raise exceptions.ConfigException( "Cloud {name} was not found.".format( name=name)) our_cloud = self.cloud_config['clouds'].get(name, dict()) if profile: our_cloud['profile'] = profile # 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
def _validate_networks(self, networks, key): value = None for net in networks: if value and net[key]: raise exceptions.ConfigException( "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
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 = ( os_exc.ConfigException()) self.assertRaises(exc.OpenStackCloudException, inventory.OpenStackInventory, cloud='supercloud') mock_config.return_value.get_one_cloud.assert_called_once_with( 'supercloud')
def _get_version_request(self, service_type, version): """Translate OCC version args to those needed by ksa adapter. If no version is requested explicitly and we have a configured version, set the version parameter and let ksa deal with expanding that to min=ver.0, max=ver.latest. If version is set, pass it through. If version is not set and we don't have a configured version, default to latest. If version is set, contains a '.', and default_microversion is not set, also pass it as a default microversion. """ version_request = _util.VersionRequest() if version == 'latest': version_request.max_api_version = 'latest' return version_request if not version: version = self.get_api_version(service_type) # Octavia doens't have a version discovery document. Hard-code an # exception to this logic for now. if not version and service_type not in ('load-balancer', ): version_request.max_api_version = 'latest' else: version_request.version = version default_microversion = self.get_default_microversion(service_type) implied_microversion = _get_implied_microversion(version) if (implied_microversion and default_microversion and implied_microversion != default_microversion): raise exceptions.ConfigException( "default_microversion of {default_microversion} was given" " for {service_type}, but api_version looks like a" " microversion as well. Please set api_version to just the" " desired major version, or omit default_microversion".format( default_microversion=default_microversion, service_type=service_type)) if implied_microversion: default_microversion = implied_microversion # If we're inferring a microversion, don't pass the whole # string in as api_version, since that tells keystoneauth # we're looking for a major api version. version_request.version = version[0] version_request.default_microversion = default_microversion return version_request
def _expand_regions(self, regions): ret = [] for region in regions: if isinstance(region, dict): # i.e. must have name key, and only name,values keys if 'name' not in region or \ not {'name', 'values'} >= set(region): raise exceptions.ConfigException( 'Invalid region entry at: %s' % region) if 'values' not in region: region['values'] = {} ret.append(copy.deepcopy(region)) else: ret.append(self._expand_region_name(region)) return ret
def get_profile(profile_name): vendor_defaults = _get_vendor_defaults() if profile_name in vendor_defaults: return vendor_defaults[profile_name].copy() profile_url = urllib.parse.urlparse(profile_name) if not profile_url.netloc: # This isn't a url, and we already don't have it. return well_known_url = _WELL_KNOWN_PATH.format( scheme=profile_url.scheme, netloc=profile_url.netloc, ) response = requests.get(well_known_url) if not response.ok: raise exceptions.ConfigException( "{profile_name} is a remote profile that could not be fetched:" " {status_code} {reason}".format( profile_name=profile_name, status_code=response.status_code, reason=response.reason)) vendor_defaults[profile_name] = None return vendor_data = response.json() name = vendor_data['name'] # Merge named and url cloud config, but make named config override the # config from the cloud so that we can supply local overrides if needed. profile = _util.merge_clouds( vendor_data['profile'], vendor_defaults.get(name, {})) # If there is (or was) a profile listed in a named config profile, it # might still be here. We just merged in content from a URL though, so # pop the key to prevent doing it again in the future. profile.pop('profile', None) # Save the data under both names so we don't reprocess this, no matter # how we're called. vendor_defaults[profile_name] = profile vendor_defaults[name] = profile return profile
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.ConfigException( '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))
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 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.ConfigException 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=self._get_envvar('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 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_region = self.get_one(argparse=options, validate=False) default_auth_type = cloud_region.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.ConfigException( "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)
def get_session_client( self, service_type, version=None, constructor=proxy.Proxy, **kwargs): """Return a prepped keystoneauth Adapter for a given service. This is useful for making direct requests calls against a 'mounted' endpoint. That is, if you do: client = get_session_client('compute') then you can do: client.get('/flavors') and it will work like you think. """ version_request = self._get_version_request(service_type, version) kwargs.setdefault('region_name', self.get_region_name(service_type)) kwargs.setdefault('connect_retries', self.get_connect_retries(service_type)) kwargs.setdefault('status_code_retries', self.get_status_code_retries(service_type)) kwargs.setdefault('statsd_prefix', self.get_statsd_prefix()) kwargs.setdefault('statsd_client', self.get_statsd_client()) kwargs.setdefault('prometheus_counter', self.get_prometheus_counter()) kwargs.setdefault( 'prometheus_histogram', self.get_prometheus_histogram()) kwargs.setdefault('influxdb_config', self._influxdb_config) kwargs.setdefault('influxdb_client', self.get_influxdb_client()) endpoint_override = self.get_endpoint(service_type) version = version_request.version min_api_version = ( kwargs.pop('min_version', None) or version_request.min_api_version) max_api_version = ( kwargs.pop('max_version', None) or version_request.max_api_version) # Older neutron has inaccessible discovery document. Nobody noticed # because neutronclient hard-codes an append of v2.0. YAY! # Also, older octavia has a similar issue. if service_type in ('network', 'load-balancer'): version = None min_api_version = None max_api_version = None if endpoint_override is None: endpoint_override = self._get_hardcoded_endpoint( service_type, constructor) client = constructor( session=self.get_session(), service_type=self.get_service_type(service_type), service_name=self.get_service_name(service_type), interface=self.get_interface(service_type), version=version, min_version=min_api_version, max_version=max_api_version, endpoint_override=endpoint_override, default_microversion=version_request.default_microversion, rate_limit=self.get_rate_limit(service_type), concurrency=self.get_concurrency(service_type), **kwargs) if version_request.default_microversion: default_microversion = version_request.default_microversion info = client.get_endpoint_data() if not discover.version_between( info.min_microversion, info.max_microversion, default_microversion ): if self.get_default_microversion(service_type): raise exceptions.ConfigException( "A default microversion for service {service_type} of" " {default_microversion} was requested, but the cloud" " only supports a minimum of {min_microversion} and" " a maximum of {max_microversion}.".format( service_type=service_type, default_microversion=default_microversion, min_microversion=discover.version_to_string( info.min_microversion), max_microversion=discover.version_to_string( info.max_microversion))) else: raise exceptions.ConfigException( "A default microversion for service {service_type} of" " {default_microversion} was requested, but the cloud" " only supports a maximum of" " only supports a minimum of {min_microversion} and" " a maximum of {max_microversion}. The default" " microversion was set because a microversion" " formatted version string, '{api_version}', was" " passed for the api_version of the service. If it" " was not intended to set a default microversion" " please remove anything other than an integer major" " version from the version setting for" " the service.".format( service_type=service_type, api_version=self.get_api_version(service_type), default_microversion=default_microversion, min_microversion=discover.version_to_string( info.min_microversion), max_microversion=discover.version_to_string( info.max_microversion))) return client
def from_conf(conf, session=None, service_types=None, **kwargs): """Create a CloudRegion from oslo.config ConfigOpts. :param oslo_config.cfg.ConfigOpts conf: An oslo.config ConfigOpts containing keystoneauth1.Adapter options in sections named according to project (e.g. [nova], not [compute]). TODO: Current behavior is to use defaults if no such section exists, which may not be what we want long term. :param keystoneauth1.session.Session session: An existing authenticated Session to use. This is currently required. TODO: Load this (and auth) from the conf. :param service_types: A list/set of service types for which to look for and process config opts. If None, all known service types are processed. Note that we will not error if a supplied service type can not be processed successfully (unless you try to use the proxy, of course). This tolerates uses where the consuming code has paths for a given service, but those paths are not exercised for given end user setups, and we do not want to generate errors for e.g. missing/invalid conf sections in those cases. We also don't check to make sure your service types are spelled correctly - caveat implementor. :param kwargs: Additional keyword arguments to be passed directly to the CloudRegion constructor. :raise openstack.exceptions.ConfigException: If session is not specified. :return: An openstack.config.cloud_region.CloudRegion. """ if not session: # TODO(mordred) Fill this in - not needed for first stab with nova raise exceptions.ConfigException("A Session must be supplied.") config_dict = kwargs.pop('config', config_defaults.get_defaults()) stm = os_service_types.ServiceTypes() for st in stm.all_types_by_service_type: if service_types is not None and st not in service_types: _disable_service( config_dict, st, reason="Not in the list of requested service_types.") continue project_name = stm.get_project_name(st) if project_name not in conf: if '-' in project_name: project_name = project_name.replace('-', '_') if project_name not in conf: _disable_service( config_dict, st, reason="No section for project '{project}' (service type " "'{service_type}') was present in the config." .format(project=project_name, service_type=st)) continue opt_dict = {} # Populate opt_dict with (appropriately processed) Adapter conf opts try: ks_load_adap.process_conf_options(conf[project_name], opt_dict) except Exception as e: # NOTE(efried): This is for (at least) a couple of scenarios: # (1) oslo_config.cfg.NoSuchOptError when ksa adapter opts are not # registered in this section. # (2) TypeError, when opts are registered but bogus (e.g. # 'interface' and 'valid_interfaces' are both present). # We may want to consider (providing a kwarg giving the caller the # option of) blowing up right away for (2) rather than letting them # get all the way to the point of trying the service and having # *that* blow up. reason = ("Encountered an exception attempting to process config " "for project '{project}' (service type " "'{service_type}'): {exception}".format( project=project_name, service_type=st, exception=e)) _logger.warning("Disabling service '{service_type}': " "{reason}".format(service_type=st, reason=reason)) _disable_service(config_dict, st, reason=reason) continue # Load them into config_dict under keys prefixed by ${service_type}_ for raw_name, opt_val in opt_dict.items(): config_name = _make_key(raw_name, st) config_dict[config_name] = opt_val return CloudRegion( session=session, config=config_dict, **kwargs)
def get_session_client(self, service_type, version=None, constructor=adapter.Adapter, **kwargs): """Return a prepped keystoneauth Adapter for a given service. This is useful for making direct requests calls against a 'mounted' endpoint. That is, if you do: client = get_session_client('compute') then you can do: client.get('/flavors') and it will work like you think. """ version_request = self._get_version_request(service_type, version) kwargs.setdefault('connect_retries', self.get_connect_retries(service_type)) kwargs.setdefault('status_code_retries', self.get_status_code_retries(service_type)) client = constructor( session=self.get_session(), service_type=self.get_service_type(service_type), service_name=self.get_service_name(service_type), interface=self.get_interface(service_type), region_name=self.region_name, version=version_request.version, min_version=version_request.min_api_version, max_version=version_request.max_api_version, endpoint_override=self.get_endpoint(service_type), default_microversion=version_request.default_microversion, **kwargs) if version_request.default_microversion: default_microversion = version_request.default_microversion info = client.get_endpoint_data() if not discover.version_between(info.min_microversion, info.max_microversion, default_microversion): if self.get_default_microversion(service_type): raise exceptions.ConfigException( "A default microversion for service {service_type} of" " {default_microversion} was requested, but the cloud" " only supports a minimum of {min_microversion} and" " a maximum of {max_microversion}.".format( service_type=service_type, default_microversion=default_microversion, min_microversion=discover.version_to_string( info.min_microversion), max_microversion=discover.version_to_string( info.max_microversion))) else: raise exceptions.ConfigException( "A default microversion for service {service_type} of" " {default_microversion} was requested, but the cloud" " only supports a maximum of" " only supports a minimum of {min_microversion} and" " a maximum of {max_microversion}. The default" " microversion was set because a microversion" " formatted version string, '{api_version}', was" " passed for the api_version of the service. If it" " was not intended to set a default microversion" " please remove anything other than an integer major" " version from the version setting for" " the service.".format( service_type=service_type, api_version=self.get_api_version(service_type), default_microversion=default_microversion, min_microversion=discover.version_to_string( info.min_microversion), max_microversion=discover.version_to_string( info.max_microversion))) return client
def get_session_client(self, service_type, version=None, constructor=_adapter.OpenStackSDKAdapter, **kwargs): """Return a prepped keystoneauth Adapter for a given service. This is useful for making direct requests calls against a 'mounted' endpoint. That is, if you do: client = get_session_client('compute') then you can do: client.get('/flavors') and it will work like you think. """ version_request = self._get_version_request(service_type, version) kwargs.setdefault('connect_retries', self.get_connect_retries(service_type)) kwargs.setdefault('status_code_retries', self.get_status_code_retries(service_type)) endpoint_override = self.get_endpoint(service_type) version = version_request.version min_api_version = (kwargs.pop('min_version', None) or version_request.min_api_version) max_api_version = (kwargs.pop('max_version', None) or version_request.max_api_version) # Older neutron has inaccessible discovery document. Nobody noticed # because neutronclient hard-codes an append of v2.0. YAY! if service_type == 'network': version = None min_api_version = None max_api_version = None if endpoint_override is None: network_adapter = constructor( session=self.get_session(), service_type=self.get_service_type(service_type), service_name=self.get_service_name(service_type), interface=self.get_interface(service_type), region_name=self.region_name, ) network_endpoint = network_adapter.get_endpoint() if not network_endpoint.rstrip().rsplit('/')[-1] == 'v2.0': if not network_endpoint.endswith('/'): network_endpoint += '/' network_endpoint = urllib.parse.urljoin( network_endpoint, 'v2.0') endpoint_override = network_endpoint client = constructor( session=self.get_session(), service_type=self.get_service_type(service_type), service_name=self.get_service_name(service_type), interface=self.get_interface(service_type), region_name=self.region_name, version=version, min_version=min_api_version, max_version=max_api_version, endpoint_override=endpoint_override, default_microversion=version_request.default_microversion, rate_limit=self.get_rate_limit(service_type), concurrency=self.get_concurrency(service_type), **kwargs) if version_request.default_microversion: default_microversion = version_request.default_microversion info = client.get_endpoint_data() if not discover.version_between(info.min_microversion, info.max_microversion, default_microversion): if self.get_default_microversion(service_type): raise exceptions.ConfigException( "A default microversion for service {service_type} of" " {default_microversion} was requested, but the cloud" " only supports a minimum of {min_microversion} and" " a maximum of {max_microversion}.".format( service_type=service_type, default_microversion=default_microversion, min_microversion=discover.version_to_string( info.min_microversion), max_microversion=discover.version_to_string( info.max_microversion))) else: raise exceptions.ConfigException( "A default microversion for service {service_type} of" " {default_microversion} was requested, but the cloud" " only supports a maximum of" " only supports a minimum of {min_microversion} and" " a maximum of {max_microversion}. The default" " microversion was set because a microversion" " formatted version string, '{api_version}', was" " passed for the api_version of the service. If it" " was not intended to set a default microversion" " please remove anything other than an integer major" " version from the version setting for" " the service.".format( service_type=service_type, api_version=self.get_api_version(service_type), default_microversion=default_microversion, min_microversion=discover.version_to_string( info.min_microversion), max_microversion=discover.version_to_string( info.max_microversion))) return client
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, load_envvars=True): self.log = _log.setup_logging('openstack.config') self._session_constructor = session_constructor self._app_name = app_name self._app_version = app_version self._load_envvars = load_envvars 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 = self._get_envvar('OS_CLIENT_CONFIG_FILE') if config_file_override: self._config_files.insert(0, config_file_override) secure_file_override = self._get_envvar('OS_CLIENT_SECURE_FILE') if secure_file_override: self._secure_files.insert(0, secure_file_override) self.defaults = self._defaults_module.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( self._get_envvar( 'OS_PREFER_IPV6', client_config.get('prefer_ipv6', client_config.get('prefer-ipv6', True)))) force_ipv4 = get_boolean( self._get_envvar( '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 = self._get_envvar('OS_CLOUD_NAME', 'envvars') if self.envvar_key in self.cloud_config['clouds']: raise exceptions.ConfigException( '"{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)) self.default_cloud = self._get_envvar('OS_CLOUD') if load_envvars: envvars = self._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 if not self.default_cloud and self.cloud_config['clouds']: if len(self.cloud_config['clouds'].keys()) == 1: # If there is only one cloud just use it. This matches envvars # behavior and allows for much less typing. # TODO(mordred) allow someone to mark a cloud as "default" in # clouds.yaml. # The next/iter thing is for python3 compat where dict.keys # returns an iterator but in python2 it's a list. self.default_cloud = next( iter(self.cloud_config['clouds'].keys())) # 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 = _util.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