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
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
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))
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)))
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
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
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
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
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')
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
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
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
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))
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)
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