def __init__(self, conf_file=None): self._base_defs = {} self._plugins = {} self._parser = None self._config_file = conf_file self.data = ConfigData() # FIXME: make dynamic? scan for more? make it's own method? # Create configuration definitions from source bconfig_def = to_bytes('%s/base.yml' % os.path.dirname(__file__)) if os.path.exists(bconfig_def): with open(bconfig_def, 'rb') as config_def: self._base_defs = yaml.safe_load(config_def) else: raise AnsibleError( "Missing base configuration definition file (bad install?): %s" % to_native(bconfig_def)) if self._config_file is None: # set config using ini self._config_file = find_ini_config_file() if self._config_file: if os.path.exists(self._config_file): # initialize parser and read config self._parse_config_file() # update constants self.update_config_data()
def __init__(self, conf_file=None, defs_file=None): self._base_defs = {} self._plugins = {} self._parsers = {} self._config_file = conf_file self.data = ConfigData() self._base_defs = self._read_config_yaml_file(defs_file or ('%s/base.yml' % os.path.dirname(__file__))) if self._config_file is None: # set config using ini self._config_file = find_ini_config_file(self.WARNINGS) # consume configuration if self._config_file: # initialize parser and read config self._parse_config_file() # update constants self.update_config_data() try: self.update_module_defaults_groups() except Exception as e: # Since this is a 2.7 preview feature, we want to have it fail as gracefully as possible when there are issues. sys.stderr.write('Could not load module_defaults_groups: %s: %s\n\n' % (type(e).__name__, e)) self.module_defaults_groups = {}
def __init__(self, conf_file=None, defs_file=None): self._base_defs = {} self._plugins = {} self._parsers = {} self._config_file = conf_file self.data = ConfigData() if defs_file is None: # Create configuration definitions from source b_defs_file = to_bytes('%s/base.yml' % os.path.dirname(__file__)) else: b_defs_file = to_bytes(defs_file) # consume definitions if os.path.exists(b_defs_file): with open(b_defs_file, 'rb') as config_def: self._base_defs = yaml_load(config_def, Loader=SafeLoader) else: raise AnsibleError("Missing base configuration definition file (bad install?): %s" % to_native(b_defs_file)) if self._config_file is None: # set config using ini self._config_file = find_ini_config_file() # consume configuration if self._config_file: if os.path.exists(self._config_file): # initialize parser and read config self._parse_config_file() # update constants self.update_config_data()
class TestConfigData(unittest.TestCase): def setUp(self): self.cdata = ConfigData() def tearDown(self): self.cdata = None def test_update_setting(self): for setting in [mykey, mykey2, mykey3]: self.cdata.update_setting(setting) self.assertEqual(setting, self.cdata._global_settings.get(setting.name)) def test_update_setting_with_plugin(self): pass def test_get_setting(self): self.cdata._global_settings = {'mykey': mykey} self.assertEqual(mykey, self.cdata.get_setting('mykey')) def test_get_settings(self): all_settings = {'mykey': mykey, 'mykey2': mykey2} self.cdata._global_settings = all_settings for setting in self.cdata.get_settings(): self.assertEqual(all_settings[setting.name], setting)
def __init__(self, conf_file=None): self.data = ConfigData() #FIXME: make dynamic? bconfig_def = to_bytes('%s/data/config.yml' % os.path.dirname(__file__)) if os.path.exists(bconfig_def): with open(bconfig_def, 'rb') as config_def: self.initial_defs = yaml.safe_load(config_def) else: raise AnsibleError( "Missing base configuration definition file (bad install?): %s" % to_native(bconfig_def)) ftype = None if conf_file is None: # set config using ini conf_file = self.find_ini_config_file() ftype = 'ini' else: ext = os.path.splitext(conf_file)[-1] if ext in ('.ini', '.cfg'): ftype = 'ini' elif ext in ('.yaml', '.yml'): ftype = 'yaml' else: raise AnsibleOptionsError( "Unsupported configuration file extension: \n{0}".format( ext)) self.parse_config(conf_file, ftype)
def __init__(self, conf_file=None): self._base_defs = {} self._plugins = {} self._parser = None self._config_file = conf_file self.data = ConfigData() # FIXME: make dynamic? scan for more? make it's own method? # Create configuration definitions from source bconfig_def = to_bytes('%s/base.yml' % os.path.dirname(__file__)) if os.path.exists(bconfig_def): with open(bconfig_def, 'rb') as config_def: self._base_defs = yaml.safe_load(config_def) else: raise AnsibleError("Missing base configuration definition file (bad install?): %s" % to_native(bconfig_def)) if self._config_file is None: # set config using ini self._config_file = find_ini_config_file() if self._config_file: if os.path.exists(self._config_file): # initialize parser and read config self._parse_config_file() # update constants self.update_config_data()
def __init__(self, conf_file=None, defs_file=None): self._base_defs = {} self._plugins = {} self._parsers = {} self._config_file = conf_file self.data = ConfigData() self._base_defs = self._read_config_yaml_file(defs_file or ('%s/base.yml' % os.path.dirname(__file__))) if self._config_file is None: # set config using ini self._config_file = find_ini_config_file(self.WARNINGS) # consume configuration if self._config_file: # initialize parser and read config self._parse_config_file() # update constants self.update_config_data()
class ConfigManager(object): UNABLE = [] DEPRECATED = [] def __init__(self, conf_file=None): self._base_defs = {} self._plugins = {} self._parser = None self._config_file = conf_file self.data = ConfigData() # FIXME: make dynamic? scan for more? make it's own method? # Create configuration definitions from source bconfig_def = to_bytes('%s/base.yml' % os.path.dirname(__file__)) if os.path.exists(bconfig_def): with open(bconfig_def, 'rb') as config_def: self._base_defs = yaml.safe_load(config_def) else: raise AnsibleError("Missing base configuration definition file (bad install?): %s" % to_native(bconfig_def)) if self._config_file is None: # set config using ini self._config_file = find_ini_config_file() if self._config_file: if os.path.exists(self._config_file): # initialize parser and read config self._parse_config_file() # update constants self.update_config_data() def _parse_config_file(self, cfile=None): ''' return flat configuration settings from file(s) ''' # TODO: take list of files with merge/nomerge if cfile is None: cfile = self._config_file ftype = get_config_type(cfile) if cfile is not None: if ftype == 'ini': self._parser = configparser.ConfigParser() try: self._parser.read(cfile) except configparser.Error as e: raise AnsibleOptionsError("Error reading config file (%s): %s" % (cfile, to_native(e))) # FIXME: this should eventually handle yaml config files #elif ftype == 'yaml': # with open(cfile, 'rb') as config_stream: # self._parser = yaml.safe_load(config_stream) else: raise AnsibleOptionsError("Unsupported configuration file type: %s" % to_native(ftype)) def _find_yaml_config_files(self): ''' Load YAML Config Files in order, check merge flags, keep origin of settings''' pass def get_plugin_options(self, plugin_type, name, variables=None): options = {} defs = self.get_configuration_definitions(plugin_type, name) for option in defs: options[option] = self.get_config_value(option, plugin_type=plugin_type, plugin_name=name, variables=variables) return options def get_configuration_definitions(self, plugin_type=None, name=None): ''' just list the possible settings, either base or for specific plugins or plugin ''' ret = {} if plugin_type is None: ret = self._base_defs elif name is None: ret = self._plugins.get(plugin_type, {}) else: ret = self._plugins.get(plugin_type, {}).get(name, {}) return ret def _loop_entries(self, container, entry_list): ''' repeat code for value entry assignment ''' value = None origin = None for entry in entry_list: name = entry.get('name') temp_value = container.get(name, None) if temp_value is not None: # only set if env var is defined value = temp_value origin = name # deal with deprecation of setting source, if used if 'deprecated' in entry: self.DEPRECATED.append((entry['name'], entry['deprecated'])) return value, origin def get_config_value(self, config, cfile=None, plugin_type=None, plugin_name=None, variables=None): ''' wrapper ''' value, _drop = self.get_config_value_and_origin(config, cfile=cfile, plugin_type=plugin_type, plugin_name=plugin_name, variables=variables) return value def get_config_value_and_origin(self, config, cfile=None, plugin_type=None, plugin_name=None, variables=None): ''' Given a config key figure out the actual value and report on the origin of the settings ''' if cfile is None: cfile = self._config_file # Note: sources that are lists listed in low to high precedence (last one wins) value = None defs = {} if plugin_type is None: defs = self._base_defs elif plugin_name is None: defs = self._plugins[plugin_type] else: defs = self._plugins[plugin_type][plugin_name] # Use 'variable overrides' if present, highest precedence, but only present when querying running play if variables: value, origin = self._loop_entries(variables, defs[config]['vars']) origin = 'var: %s' % origin # env vars are next precedence if value is None and defs[config].get('env'): value, origin = self._loop_entries(os.environ, defs[config]['env']) origin = 'env: %s' % origin # try config file entries next, if we have one if value is None and cfile is not None: ftype = get_config_type(cfile) if ftype and defs[config].get(ftype): if ftype == 'ini': # load from ini config try: # FIXME: generaelize _loop_entries to allow for files also, most of this code is dupe for ini_entry in defs[config]['ini']: temp_value = get_ini_config_value(self._parser, ini_entry) if temp_value is not None: value = temp_value origin = cfile if 'deprecated' in ini_entry: self.DEPRECATED.append(('[%s]%s' % (ini_entry['section'], ini_entry['key']), ini_entry['deprecated'])) except Exception as e: sys.stderr.write("Error while loading ini config %s: %s" % (cfile, to_native(e))) elif ftype == 'yaml': # FIXME: implement, also , break down key from defs (. notation???) origin = cfile ''' # for plugins, try using existing constants, this is for backwards compatiblity if plugin_name and defs[config].get('constants'): value, origin = self._loop_entries(self.data, defs[config]['constants']) origin = 'constant: %s' % origin ''' # set default if we got here w/o a value if value is None: value = defs[config].get('default') origin = 'default' # skip typing as this is a temlated default that will be resolved later in constants, which has needed vars if plugin_type is None and isinstance(value, string_types) and (value.startswith('{{') and value.endswith('}}')): return value, origin # ensure correct type try: value = ensure_type(value, defs[config].get('type')) except Exception as e: self.UNABLE.append(config) # deal with deprecation of the setting if 'deprecated' in defs[config] and origin != 'default': self.DEPRECATED.append((config, defs[config].get('deprecated'))) return value, origin def initialize_plugin_configuration_definitions(self, plugin_type, name, defs): if plugin_type not in self._plugins: self._plugins[plugin_type] = {} self._plugins[plugin_type][name] = defs def update_config_data(self, defs=None, configfile=None): ''' really: update constants ''' if defs is None: defs = self._base_defs if configfile is None: configfile = self._config_file if not isinstance(defs, dict): raise AnsibleOptionsError("Invalid configuration definition type: %s for %s" % (type(defs), defs)) # update the constant for config file self.data.update_setting(Setting('CONFIG_FILE', configfile, '', 'string')) origin = None # env and config defs can have several entries, ordered in list from lowest to highest precedence for config in defs: if not isinstance(defs[config], dict): raise AnsibleOptionsError("Invalid configuration definition '%s': type is %s" % (to_native(config), type(defs[config]))) # get value and origin value, origin = self.get_config_value_and_origin(config, configfile) # set the constant self.data.update_setting(Setting(config, value, origin, defs[config].get('type', 'string')))
class ConfigManager(object): DEPRECATED = [] WARNINGS = set() def __init__(self, conf_file=None, defs_file=None): self._base_defs = {} self._plugins = {} self._parsers = {} self._config_file = conf_file self.data = ConfigData() self._base_defs = self._read_config_yaml_file(defs_file or ('%s/base.yml' % os.path.dirname(__file__))) if self._config_file is None: # set config using ini self._config_file = find_ini_config_file(self.WARNINGS) # consume configuration if self._config_file: # initialize parser and read config self._parse_config_file() # update constants self.update_config_data() try: self.update_module_defaults_groups() except Exception as e: # Since this is a 2.7 preview feature, we want to have it fail as gracefully as possible when there are issues. sys.stderr.write('Could not load module_defaults_groups: %s: %s\n\n' % (type(e).__name__, e)) self.module_defaults_groups = {} def _read_config_yaml_file(self, yml_file): # TODO: handle relative paths as relative to the directory containing the current playbook instead of CWD # Currently this is only used with absolute paths to the `ansible/config` directory yml_file = to_bytes(yml_file) if os.path.exists(yml_file): with open(yml_file, 'rb') as config_def: return yaml_load(config_def, Loader=SafeLoader) or {} raise AnsibleError( "Missing base YAML definition file (bad install?): %s" % to_native(yml_file)) def _parse_config_file(self, cfile=None): ''' return flat configuration settings from file(s) ''' # TODO: take list of files with merge/nomerge if cfile is None: cfile = self._config_file ftype = get_config_type(cfile) if cfile is not None: if ftype == 'ini': self._parsers[cfile] = configparser.ConfigParser() with open(to_bytes(cfile), 'rb') as f: try: cfg_text = to_text(f.read(), errors='surrogate_or_strict') except UnicodeError as e: raise AnsibleOptionsError("Error reading config file(%s) because the config file was not utf8 encoded: %s" % (cfile, to_native(e))) try: if PY3: self._parsers[cfile].read_string(cfg_text) else: cfg_file = io.StringIO(cfg_text) self._parsers[cfile].readfp(cfg_file) except configparser.Error as e: raise AnsibleOptionsError("Error reading config file (%s): %s" % (cfile, to_native(e))) # FIXME: this should eventually handle yaml config files # elif ftype == 'yaml': # with open(cfile, 'rb') as config_stream: # self._parsers[cfile] = yaml.safe_load(config_stream) else: raise AnsibleOptionsError("Unsupported configuration file type: %s" % to_native(ftype)) def _find_yaml_config_files(self): ''' Load YAML Config Files in order, check merge flags, keep origin of settings''' pass def get_plugin_options(self, plugin_type, name, keys=None, variables=None, direct=None): options = {} defs = self.get_configuration_definitions(plugin_type, name) for option in defs: options[option] = self.get_config_value(option, plugin_type=plugin_type, plugin_name=name, keys=keys, variables=variables, direct=direct) return options def get_plugin_vars(self, plugin_type, name): pvars = [] for pdef in self.get_configuration_definitions(plugin_type, name).values(): if 'vars' in pdef and pdef['vars']: for var_entry in pdef['vars']: pvars.append(var_entry['name']) return pvars def get_configuration_definition(self, name, plugin_type=None, plugin_name=None): ret = {} if plugin_type is None: ret = self._base_defs.get(name, None) elif plugin_name is None: ret = self._plugins.get(plugin_type, {}).get(name, None) else: ret = self._plugins.get(plugin_type, {}).get(plugin_name, {}).get(name, None) return ret def get_configuration_definitions(self, plugin_type=None, name=None): ''' just list the possible settings, either base or for specific plugins or plugin ''' ret = {} if plugin_type is None: ret = self._base_defs elif name is None: ret = self._plugins.get(plugin_type, {}) else: ret = self._plugins.get(plugin_type, {}).get(name, {}) return ret def _loop_entries(self, container, entry_list): ''' repeat code for value entry assignment ''' value = None origin = None for entry in entry_list: name = entry.get('name') try: temp_value = container.get(name, None) except UnicodeEncodeError: self.WARNINGS.add(u'value for config entry {0} contains invalid characters, ignoring...'.format(to_text(name))) continue if temp_value is not None: # only set if entry is defined in container # inline vault variables should be converted to a text string if isinstance(temp_value, AnsibleVaultEncryptedUnicode): temp_value = to_text(temp_value, errors='surrogate_or_strict') value = temp_value origin = name # deal with deprecation of setting source, if used if 'deprecated' in entry: self.DEPRECATED.append((entry['name'], entry['deprecated'])) return value, origin def get_config_value(self, config, cfile=None, plugin_type=None, plugin_name=None, keys=None, variables=None, direct=None): ''' wrapper ''' try: value, _drop = self.get_config_value_and_origin(config, cfile=cfile, plugin_type=plugin_type, plugin_name=plugin_name, keys=keys, variables=variables, direct=direct) except AnsibleError: raise except Exception as e: raise AnsibleError("Unhandled exception when retrieving %s:\n%s" % (config, to_native(e)), orig_exc=e) return value def get_config_value_and_origin(self, config, cfile=None, plugin_type=None, plugin_name=None, keys=None, variables=None, direct=None): ''' Given a config key figure out the actual value and report on the origin of the settings ''' if cfile is None: # use default config cfile = self._config_file # Note: sources that are lists listed in low to high precedence (last one wins) value = None origin = None defs = self.get_configuration_definitions(plugin_type, plugin_name) if config in defs: # direct setting via plugin arguments, can set to None so we bypass rest of processing/defaults direct_aliases = [] if direct: direct_aliases = [direct[alias] for alias in defs[config].get('aliases', []) if alias in direct] if direct and config in direct: value = direct[config] origin = 'Direct' elif direct and direct_aliases: value = direct_aliases[0] origin = 'Direct' else: # Use 'variable overrides' if present, highest precedence, but only present when querying running play if variables and defs[config].get('vars'): value, origin = self._loop_entries(variables, defs[config]['vars']) origin = 'var: %s' % origin # use playbook keywords if you have em if value is None and keys and config in keys: value, origin = keys[config], 'keyword' origin = 'keyword: %s' % origin # env vars are next precedence if value is None and defs[config].get('env'): value, origin = self._loop_entries(py3compat.environ, defs[config]['env']) origin = 'env: %s' % origin # try config file entries next, if we have one if self._parsers.get(cfile, None) is None: self._parse_config_file(cfile) if value is None and cfile is not None: ftype = get_config_type(cfile) if ftype and defs[config].get(ftype): if ftype == 'ini': # load from ini config try: # FIXME: generalize _loop_entries to allow for files also, most of this code is dupe for ini_entry in defs[config]['ini']: temp_value = get_ini_config_value(self._parsers[cfile], ini_entry) if temp_value is not None: value = temp_value origin = cfile if 'deprecated' in ini_entry: self.DEPRECATED.append(('[%s]%s' % (ini_entry['section'], ini_entry['key']), ini_entry['deprecated'])) except Exception as e: sys.stderr.write("Error while loading ini config %s: %s" % (cfile, to_native(e))) elif ftype == 'yaml': # FIXME: implement, also , break down key from defs (. notation???) origin = cfile # set default if we got here w/o a value if value is None: if defs[config].get('required', False): if not plugin_type or config not in INTERNAL_DEFS.get(plugin_type, {}): raise AnsibleError("No setting was provided for required configuration %s" % to_native(_get_entry(plugin_type, plugin_name, config))) else: value = defs[config].get('default') origin = 'default' # skip typing as this is a templated default that will be resolved later in constants, which has needed vars if plugin_type is None and isinstance(value, string_types) and (value.startswith('{{') and value.endswith('}}')): return value, origin # ensure correct type, can raise exceptions on mismatched types try: value = ensure_type(value, defs[config].get('type'), origin=origin) except ValueError as e: if origin.startswith('env:') and value == '': # this is empty env var for non string so we can set to default origin = 'default' value = ensure_type(defs[config].get('default'), defs[config].get('type'), origin=origin) else: raise AnsibleOptionsError('Invalid type for configuration option %s: %s' % (to_native(_get_entry(plugin_type, plugin_name, config)), to_native(e))) # deal with deprecation of the setting if 'deprecated' in defs[config] and origin != 'default': self.DEPRECATED.append((config, defs[config].get('deprecated'))) else: raise AnsibleError('Requested entry (%s) was not defined in configuration.' % to_native(_get_entry(plugin_type, plugin_name, config))) return value, origin def initialize_plugin_configuration_definitions(self, plugin_type, name, defs): if plugin_type not in self._plugins: self._plugins[plugin_type] = {} self._plugins[plugin_type][name] = defs def update_module_defaults_groups(self): defaults_config = self._read_config_yaml_file( '%s/module_defaults.yml' % os.path.join(os.path.dirname(__file__)) ) if defaults_config.get('version') not in ('1', '1.0', 1, 1.0): raise AnsibleError('module_defaults.yml has an invalid version "%s" for configuration. Could be a bad install.' % defaults_config.get('version')) self.module_defaults_groups = defaults_config.get('groupings', {}) def update_config_data(self, defs=None, configfile=None): ''' really: update constants ''' if defs is None: defs = self._base_defs if configfile is None: configfile = self._config_file if not isinstance(defs, dict): raise AnsibleOptionsError("Invalid configuration definition type: %s for %s" % (type(defs), defs)) # update the constant for config file self.data.update_setting(Setting('CONFIG_FILE', configfile, '', 'string')) origin = None # env and config defs can have several entries, ordered in list from lowest to highest precedence for config in defs: if not isinstance(defs[config], dict): raise AnsibleOptionsError("Invalid configuration definition '%s': type is %s" % (to_native(config), type(defs[config]))) # get value and origin try: value, origin = self.get_config_value_and_origin(config, configfile) except Exception as e: # Printing the problem here because, in the current code: # (1) we can't reach the error handler for AnsibleError before we # hit a different error due to lack of working config. # (2) We don't have access to display yet because display depends on config # being properly loaded. # # If we start getting double errors printed from this section of code, then the # above problem #1 has been fixed. Revamp this to be more like the try: except # in get_config_value() at that time. sys.stderr.write("Unhandled error:\n %s\n\n" % traceback.format_exc()) raise AnsibleError("Invalid settings supplied for %s: %s\n" % (config, to_native(e)), orig_exc=e) # set the constant self.data.update_setting(Setting(config, value, origin, defs[config].get('type', 'string')))
class ConfigManager(object): UNABLE = [] DEPRECATED = [] def __init__(self, conf_file=None, defs_file=None): self._base_defs = {} self._plugins = {} self._parser = None self._config_file = conf_file self.data = ConfigData() if defs_file is None: # Create configuration definitions from source b_defs_file = to_bytes('%s/base.yml' % os.path.dirname(__file__)) else: b_defs_file = to_bytes(defs_file) # consume definitions if os.path.exists(b_defs_file): with open(b_defs_file, 'rb') as config_def: self._base_defs = yaml.safe_load(config_def) else: raise AnsibleError( "Missing base configuration definition file (bad install?): %s" % to_native(b_defs_file)) if self._config_file is None: # set config using ini self._config_file = find_ini_config_file() # consume configuration if self._config_file: if os.path.exists(self._config_file): # initialize parser and read config self._parse_config_file() # update constants self.update_config_data() def _parse_config_file(self, cfile=None): ''' return flat configuration settings from file(s) ''' # TODO: take list of files with merge/nomerge if cfile is None: cfile = self._config_file ftype = get_config_type(cfile) if cfile is not None: if ftype == 'ini': self._parser = configparser.ConfigParser() try: self._parser.read(cfile) except configparser.Error as e: raise AnsibleOptionsError( "Error reading config file (%s): %s" % (cfile, to_native(e))) # FIXME: this should eventually handle yaml config files # elif ftype == 'yaml': # with open(cfile, 'rb') as config_stream: # self._parser = yaml.safe_load(config_stream) else: raise AnsibleOptionsError( "Unsupported configuration file type: %s" % to_native(ftype)) def _find_yaml_config_files(self): ''' Load YAML Config Files in order, check merge flags, keep origin of settings''' pass def get_plugin_options(self, plugin_type, name, variables=None): options = {} defs = self.get_configuration_definitions(plugin_type, name) for option in defs: options[option] = self.get_config_value(option, plugin_type=plugin_type, plugin_name=name, variables=variables) return options def get_configuration_definitions(self, plugin_type=None, name=None): ''' just list the possible settings, either base or for specific plugins or plugin ''' ret = {} if plugin_type is None: ret = self._base_defs elif name is None: ret = self._plugins.get(plugin_type, {}) else: ret = self._plugins.get(plugin_type, {}).get(name, {}) return ret def _loop_entries(self, container, entry_list): ''' repeat code for value entry assignment ''' value = None origin = None for entry in entry_list: name = entry.get('name') temp_value = container.get(name, None) if temp_value is not None: # only set if env var is defined value = temp_value origin = name # deal with deprecation of setting source, if used if 'deprecated' in entry: self.DEPRECATED.append( (entry['name'], entry['deprecated'])) return value, origin def get_config_value(self, config, cfile=None, plugin_type=None, plugin_name=None, variables=None): ''' wrapper ''' value, _drop = self.get_config_value_and_origin( config, cfile=cfile, plugin_type=plugin_type, plugin_name=plugin_name, variables=variables) return value def get_config_value_and_origin(self, config, cfile=None, plugin_type=None, plugin_name=None, variables=None): ''' Given a config key figure out the actual value and report on the origin of the settings ''' if cfile is None: cfile = self._config_file else: self._parse_config_file(cfile) # Note: sources that are lists listed in low to high precedence (last one wins) value = None origin = None defs = {} if plugin_type is None: defs = self._base_defs elif plugin_name is None: defs = self._plugins[plugin_type] else: defs = self._plugins[plugin_type][plugin_name] if config in defs: # Use 'variable overrides' if present, highest precedence, but only present when querying running play if variables: value, origin = self._loop_entries(variables, defs[config]['vars']) origin = 'var: %s' % origin # env vars are next precedence if value is None and defs[config].get('env'): value, origin = self._loop_entries(os.environ, defs[config]['env']) origin = 'env: %s' % origin # try config file entries next, if we have one if value is None and cfile is not None: ftype = get_config_type(cfile) if ftype and defs[config].get(ftype): if ftype == 'ini': # load from ini config try: # FIXME: generalize _loop_entries to allow for files also, most of this code is dupe for ini_entry in defs[config]['ini']: temp_value = get_ini_config_value( self._parser, ini_entry) if temp_value is not None: value = temp_value origin = cfile if 'deprecated' in ini_entry: self.DEPRECATED.append( ('[%s]%s' % (ini_entry['section'], ini_entry['key']), ini_entry['deprecated'])) except Exception as e: sys.stderr.write( "Error while loading ini config %s: %s" % (cfile, to_native(e))) elif ftype == 'yaml': # FIXME: implement, also , break down key from defs (. notation???) origin = cfile ''' # for plugins, try using existing constants, this is for backwards compatiblity if plugin_name and defs[config].get('constants'): value, origin = self._loop_entries(self.data, defs[config]['constants']) origin = 'constant: %s' % origin ''' # set default if we got here w/o a value if value is None: value = defs[config].get('default') origin = 'default' # skip typing as this is a temlated default that will be resolved later in constants, which has needed vars if plugin_type is None and isinstance( value, string_types) and (value.startswith('{{') and value.endswith('}}')): return value, origin # ensure correct type try: value = ensure_type(value, defs[config].get('type'), origin=origin) except Exception as e: self.UNABLE.append(config) # deal with deprecation of the setting if 'deprecated' in defs[config] and origin != 'default': self.DEPRECATED.append( (config, defs[config].get('deprecated'))) else: raise AnsibleError( 'Requested option %s was not defined in configuration' % to_native(config)) return value, origin def initialize_plugin_configuration_definitions(self, plugin_type, name, defs): if plugin_type not in self._plugins: self._plugins[plugin_type] = {} self._plugins[plugin_type][name] = defs def update_config_data(self, defs=None, configfile=None): ''' really: update constants ''' if defs is None: defs = self._base_defs if configfile is None: configfile = self._config_file if not isinstance(defs, dict): raise AnsibleOptionsError( "Invalid configuration definition type: %s for %s" % (type(defs), defs)) # update the constant for config file self.data.update_setting( Setting('CONFIG_FILE', configfile, '', 'string')) origin = None # env and config defs can have several entries, ordered in list from lowest to highest precedence for config in defs: if not isinstance(defs[config], dict): raise AnsibleOptionsError( "Invalid configuration definition '%s': type is %s" % (to_native(config), type(defs[config]))) # get value and origin value, origin = self.get_config_value_and_origin( config, configfile) # set the constant self.data.update_setting( Setting(config, value, origin, defs[config].get('type', 'string')))
class ConfigManager(object): UNABLE = {} DEPRECATED = [] WARNINGS = set() def __init__(self, conf_file=None, defs_file=None): self._base_defs = {} self._plugins = {} self._parsers = {} self._config_file = conf_file self.data = ConfigData() if defs_file is None: # Create configuration definitions from source b_defs_file = to_bytes('%s/base.yml' % os.path.dirname(__file__)) else: b_defs_file = to_bytes(defs_file) # consume definitions if os.path.exists(b_defs_file): with open(b_defs_file, 'rb') as config_def: self._base_defs = yaml_load(config_def, Loader=SafeLoader) else: raise AnsibleError( "Missing base configuration definition file (bad install?): %s" % to_native(b_defs_file)) if self._config_file is None: # set config using ini self._config_file = find_ini_config_file(self.WARNINGS) # consume configuration if self._config_file: if os.path.exists(self._config_file): # initialize parser and read config self._parse_config_file() # update constants self.update_config_data() def _parse_config_file(self, cfile=None): ''' return flat configuration settings from file(s) ''' # TODO: take list of files with merge/nomerge if cfile is None: cfile = self._config_file ftype = get_config_type(cfile) if cfile is not None: if ftype == 'ini': self._parsers[cfile] = configparser.ConfigParser() with open(cfile, 'rb') as f: try: cfg_text = to_text(f.read(), errors='surrogate_or_strict') except UnicodeError as e: raise AnsibleOptionsError( "Error reading config file(%s) because the config file was not utf8 encoded: %s" % (cfile, to_native(e))) try: self._parsers[cfile].read(cfile) except configparser.Error as e: raise AnsibleOptionsError( "Error reading config file (%s): %s" % (cfile, to_native(e))) # FIXME: this should eventually handle yaml config files # elif ftype == 'yaml': # with open(cfile, 'rb') as config_stream: # self._parsers[cfile] = yaml.safe_load(config_stream) else: raise AnsibleOptionsError( "Unsupported configuration file type: %s" % to_native(ftype)) def _find_yaml_config_files(self): ''' Load YAML Config Files in order, check merge flags, keep origin of settings''' pass def get_plugin_options(self, plugin_type, name, keys=None, variables=None, direct=None): options = {} defs = self.get_configuration_definitions(plugin_type, name) for option in defs: options[option] = self.get_config_value(option, plugin_type=plugin_type, plugin_name=name, keys=keys, variables=variables, direct=direct) return options def get_plugin_vars(self, plugin_type, name): pvars = [] for pdef in self.get_configuration_definitions(plugin_type, name).values(): if 'vars' in pdef and pdef['vars']: for var_entry in pdef['vars']: pvars.append(var_entry['name']) return pvars def get_configuration_definitions(self, plugin_type=None, name=None): ''' just list the possible settings, either base or for specific plugins or plugin ''' ret = {} if plugin_type is None: ret = self._base_defs elif name is None: ret = self._plugins.get(plugin_type, {}) else: ret = self._plugins.get(plugin_type, {}).get(name, {}) return ret def _loop_entries(self, container, entry_list): ''' repeat code for value entry assignment ''' value = None origin = None for entry in entry_list: name = entry.get('name') temp_value = container.get(name, None) if temp_value is not None: # only set if env var is defined value = temp_value origin = name # deal with deprecation of setting source, if used if 'deprecated' in entry: self.DEPRECATED.append( (entry['name'], entry['deprecated'])) return value, origin def get_config_value(self, config, cfile=None, plugin_type=None, plugin_name=None, keys=None, variables=None, direct=None): ''' wrapper ''' try: value, _drop = self.get_config_value_and_origin( config, cfile=cfile, plugin_type=plugin_type, plugin_name=plugin_name, keys=keys, variables=variables, direct=direct) except Exception as e: raise AnsibleError("Invalid settings supplied for %s: %s" % (config, to_native(e))) return value def get_config_value_and_origin(self, config, cfile=None, plugin_type=None, plugin_name=None, keys=None, variables=None, direct=None): ''' Given a config key figure out the actual value and report on the origin of the settings ''' if cfile is None: # use default config cfile = self._config_file # Note: sources that are lists listed in low to high precedence (last one wins) value = None origin = None defs = {} if plugin_type is None: defs = self._base_defs elif plugin_name is None: defs = self._plugins[plugin_type] else: defs = self._plugins[plugin_type][plugin_name] if config in defs: # direct setting via plugin arguments, can set to None so we bypass rest of processing/defaults if direct and config in direct: value = direct[config] origin = 'Direct' else: # Use 'variable overrides' if present, highest precedence, but only present when querying running play if variables and defs[config].get('vars'): value, origin = self._loop_entries(variables, defs[config]['vars']) origin = 'var: %s' % origin # use playbook keywords if you have em if value is None and keys and defs[config].get('keywords'): value, origin = self._loop_entries( keys, defs[config]['keywords']) origin = 'keyword: %s' % origin # env vars are next precedence if value is None and defs[config].get('env'): value, origin = self._loop_entries(os.environ, defs[config]['env']) origin = 'env: %s' % origin # try config file entries next, if we have one if self._parsers.get(cfile, None) is None: self._parse_config_file(cfile) if value is None and cfile is not None: ftype = get_config_type(cfile) if ftype and defs[config].get(ftype): if ftype == 'ini': # load from ini config try: # FIXME: generalize _loop_entries to allow for files also, most of this code is dupe for ini_entry in defs[config]['ini']: temp_value = get_ini_config_value( self._parsers[cfile], ini_entry) if temp_value is not None: value = temp_value origin = cfile if 'deprecated' in ini_entry: self.DEPRECATED.append( ('[%s]%s' % (ini_entry['section'], ini_entry['key']), ini_entry['deprecated'])) except Exception as e: sys.stderr.write( "Error while loading ini config %s: %s" % (cfile, to_native(e))) elif ftype == 'yaml': # FIXME: implement, also , break down key from defs (. notation???) origin = cfile # set default if we got here w/o a value if value is None: if defs[config].get('required', False): entry = '' if plugin_type: entry += 'plugin_type: %s ' % plugin_type if plugin_name: entry += 'plugin: %s ' % plugin_name entry += 'setting: %s ' % config if not plugin_type or config not in INTERNAL_DEFS.get( plugin_type, {}): raise AnsibleError( "No setting was provided for required configuration %s" % (entry)) else: value = defs[config].get('default') origin = 'default' # skip typing as this is a temlated default that will be resolved later in constants, which has needed vars if plugin_type is None and isinstance( value, string_types) and (value.startswith('{{') and value.endswith('}}')): return value, origin # ensure correct type, can raise exceptoins on mismatched types value = ensure_type(value, defs[config].get('type'), origin=origin) # deal with deprecation of the setting if 'deprecated' in defs[config] and origin != 'default': self.DEPRECATED.append( (config, defs[config].get('deprecated'))) else: raise AnsibleError( 'Requested option %s was not defined in configuration' % to_native(config)) return value, origin def initialize_plugin_configuration_definitions(self, plugin_type, name, defs): if plugin_type not in self._plugins: self._plugins[plugin_type] = {} self._plugins[plugin_type][name] = defs def update_config_data(self, defs=None, configfile=None): ''' really: update constants ''' if defs is None: defs = self._base_defs if configfile is None: configfile = self._config_file if not isinstance(defs, dict): raise AnsibleOptionsError( "Invalid configuration definition type: %s for %s" % (type(defs), defs)) # update the constant for config file self.data.update_setting( Setting('CONFIG_FILE', configfile, '', 'string')) origin = None # env and config defs can have several entries, ordered in list from lowest to highest precedence for config in defs: if not isinstance(defs[config], dict): raise AnsibleOptionsError( "Invalid configuration definition '%s': type is %s" % (to_native(config), type(defs[config]))) # get value and origin try: value, origin = self.get_config_value_and_origin( config, configfile) except Exception as e: # when building constants.py we ignore invalid configs # CLI takes care of warnings once 'display' is loaded self.UNABLE[config] = to_text(e) continue # set the constant self.data.update_setting( Setting(config, value, origin, defs[config].get('type', 'string')))
def setUp(self): self.cdata = ConfigData()
class ConfigManager(object): UNABLE = [] DEPRECATED = [] def __init__(self, conf_file=None): self._base_defs = {} self._plugins = {} self._parser = None self._config_file = conf_file self.data = ConfigData() #FIXME: make dynamic? scan for more? make it's own method? # Create configuration definitions from source bconfig_def = to_bytes('%s/base.yml' % os.path.dirname(__file__)) if os.path.exists(bconfig_def): with open(bconfig_def, 'rb') as config_def: self._base_defs = yaml.safe_load(config_def) else: raise AnsibleError( "Missing base configuration definition file (bad install?): %s" % to_native(bconfig_def)) if self._config_file is None: # set config using ini self._config_file = find_ini_config_file() if self._config_file: if os.path.exists(self._config_file): # initialize parser and read config self._parse_config_file() # update constants self.update_config_data() def _parse_config_file(self, cfile=None): ''' return flat configuration settings from file(s) ''' # TODO: take list of files with merge/nomerge if cfile is None: cfile = self._config_file ftype = get_config_type(cfile) if cfile is not None: if ftype == 'ini': self._parser = configparser.ConfigParser() try: self._parser.read(cfile) except configparser.Error as e: raise AnsibleOptionsError( "Error reading config file (%s): %s" % (cfile, to_native(e))) # FIXME: this should eventually handle yaml config files #elif ftype == 'yaml': # with open(cfile, 'rb') as config_stream: # self._parser = yaml.safe_load(config_stream) else: raise AnsibleOptionsError( "Unsupported configuration file type: %s" % to_native(ftype)) def _find_yaml_config_files(self): ''' Load YAML Config Files in order, check merge flags, keep origin of settings''' pass def get_configuration_definitions(self, plugin_type=None, name=None): ''' just list the possible settings, either base or for specific plugins or plugin ''' ret = {} if plugin_type is None: ret = self._base_defs elif name is None: ret = self._plugins.get(plugin_type, {}) else: ret = {name: self._plugins.get(plugin_type, {}).get(name, {})} return ret def _loop_entries(self, container, entry_list): ''' repeat code for value entry assignment ''' value = None origin = None for entry in entry_list: name = entry.get('name') temp_value = container.get(name, None) if temp_value is not None: # only set if env var is defined value = temp_value origin = name # deal with deprecation of setting source, if used #FIXME: if entry.get('deprecated'): return value, origin def get_config_value(self, config, cfile=None, plugin_type=None, plugin_name=None, variables=None): ''' wrapper ''' value, _drop = self.get_config_value_and_origin( config, cfile=cfile, plugin_type=plugin_type, plugin_name=plugin_name, variables=variables) return value def get_config_value_and_origin(self, config, cfile=None, plugin_type=None, plugin_name=None, variables=None): ''' Given a config key figure out the actual value and report on the origin of the settings ''' if cfile is None: cfile = self._config_file # Note: sources that are lists listed in low to high precedence (last one wins) value = None defs = {} if plugin_type is None: defs = self._base_defs elif plugin_name is None: defs = self._plugins[plugin_type] else: defs = self._plugins[plugin_type][plugin_name] # Use 'variable overrides' if present, highest precedence, but only present when querying running play if variables: value, origin = self._loop_entries(variables, defs[config]['vars']) origin = 'var: %s' % origin # env vars are next precedence if value is None and defs[config].get('env'): value, origin = self._loop_entries(os.environ, defs[config]['env']) origin = 'env: %s' % origin # try config file entries next, if we have one if value is None and cfile is not None: ftype = get_config_type(cfile) if ftype and defs[config].get(ftype): if ftype == 'ini': # load from ini config try: # FIXME: generaelize _loop_entries to allow for files also for ini_entry in defs[config]['ini']: value = get_ini_config_value( self._parser, ini_entry) origin = cfile #FIXME: if ini_entry.get('deprecated'): except Exception as e: sys.stderr.write( "Error while loading ini config %s: %s" % (cfile, to_native(e))) elif ftype == 'yaml': pass # FIXME: implement, also , break down key from defs (. notation???) origin = cfile ''' # for plugins, try using existing constants, this is for backwards compatiblity if plugin_name and defs[config].get('constants'): value, origin = self._loop_entries(self.data, defs[config]['constants']) origin = 'constant: %s' % origin ''' # set default if we got here w/o a value if value is None: value = defs[config].get('default') origin = 'default' # FIXME: moved eval to constants as this does not have access to previous vars if plugin_type is None and isinstance( value, string_types) and (value.startswith('eval(') and value.endswith(')')): return value, origin #default_value = defs[config].get('default') #if plugin_type is None and isinstance(default_value, string_types) and (default_value.startswith('eval(') and default_value.endswith(')')): # try: # eval_string = default_value.replace('eval(', '', 1)[:-1] # value = eval(eval_string) # FIXME: safe eval? # except: # value = default_value #else: # value = default_value # ensure correct type try: value = ensure_type(value, defs[config].get('type')) except Exception as e: self.UNABLE.append(config) # deal with deprecation of the setting if defs[config].get('deprecated') and origin != 'default': self.DEPRECATED.append((config, defs[config].get('deprecated'))) return value, origin def update_plugin_config(self, plugin_type, name, defs): ''' really: update constants ''' # no sense? self.initialize_plugin_configuration_definitions( plugin_type, name, defs) self.update_config_data(defs) def initialize_plugin_configuration_definitions(self, plugin_type, name, defs): if plugin_type not in self._plugins: self._plugins[plugin_type] = {} self._plugins[plugin_type][name] = defs def update_config_data(self, defs=None, configfile=None): ''' really: update constants ''' if defs is None: defs = self._base_defs if configfile is None: configfile = self._config_file if not isinstance(defs, dict): raise AnsibleOptionsError( "Invalid configuration definition type: %s for %s" % (type(defs), defs)) # update the constant for config file self.data.update_setting(Setting('CONFIG_FILE', configfile, '')) origin = None # env and config defs can have several entries, ordered in list from lowest to highest precedence for config in defs: if not isinstance(defs[config], dict): raise AnsibleOptionsError( "Invalid configuration definition '%s': type is %s" % (to_native(config), type(defs[config]))) # get value and origin value, origin = self.get_config_value_and_origin( config, configfile) # set the constant self.data.update_setting(Setting(config, value, origin)) # FIXME: find better way to do this by passing back to where display is available if self.UNABLE: sys.stderr.write("Unable to set correct type for:\n\t%s\n" % '\n\t'.join(self.UNABLE)) if self.DEPRECATED: for k, reason in self.DEPRECATED: sys.stderr.write( "[DEPRECATED] %(k)s: %(why)s. It will be removed in %(version)s. As alternative use one of [%(alternatives)s]\n" % dict(k=k, **reason))
class ConfigManager(object): def __init__(self, conf_file=None): self.data = ConfigData() #FIXME: make dynamic? bconfig_def = to_bytes('%s/data/config.yml' % os.path.dirname(__file__)) if os.path.exists(bconfig_def): with open(bconfig_def, 'rb') as config_def: self.initial_defs = yaml.safe_load(config_def) else: raise AnsibleError( "Missing base configuration definition file (bad install?): %s" % to_native(bconfig_def)) ftype = None if conf_file is None: # set config using ini conf_file = self.find_ini_config_file() ftype = 'ini' else: ext = os.path.splitext(conf_file)[-1] if ext in ('.ini', '.cfg'): ftype = 'ini' elif ext in ('.yaml', '.yml'): ftype = 'yaml' else: raise AnsibleOptionsError( "Unsupported configuration file extension: \n{0}".format( ext)) self.parse_config(conf_file, ftype) def parse_config(self, cfile, ftype): # TODO: take list of files with merge/nomerge parser = None if ftype == 'ini': parser = configparser.ConfigParser() try: parser.read(cfile) except configparser.Error as e: raise AnsibleOptionsError( "Error reading config file: \n{0}".format(e)) elif ftype == 'yaml': with open(cfile, 'rb') as config_stream: parser = yaml.safe_load(config_stream) else: raise AnsibleOptionsError( "Unsupported configuration file type: \n{0}".format(ftype)) self.update_config(cfile, self.initial_defs, parser, ftype) def update_config(self, configfile, defs, parser, ftype): # update the constant for config file self.data.update_setting(Setting('CONFIG_FILE', configfile, '')) origin = None # env and config defs can have several entries, ordered in list from lowest to highest precedence for config in self.initial_defs: value = None # env vars are highest precedence if defs[config].get('env'): try: for env_var in defs[config]['env']: env_value = os.environ.get(env_var.get('name'), None) if env_value is not None: # only set if env var is defined value = env_value origin = 'env: %s' % env_var.get('name') except: sys.stderr.write( "Error while loading environment configs for %s\n" % config) # try config file entries next if value is None and defs[config].get(ftype): if ftype == 'ini': # load from ini config try: value = get_ini_config(parser, defs[config]['ini']) origin = configfile except Exception as e: sys.stderr.write( "Error while loading ini config %s: %s" % (configfile, str(e))) elif ftype == 'yaml': # FIXME: break down key from defs (. notation???) key = 'name' value = parser.get(key) origin = configfile # set default if we got here w/o a value if value is None: value = defs[config].get('default') origin = 'default' # ensure correct type try: value = self.ensure_type(value, defs[config].get('value_type')) except: sys.stderr.write( "Unable to set correct type for %s, skipping" % config) continue # set the constant self.data.update_setting(Setting(config, value, origin)) def find_ini_config_file(self): ''' Load Config File order(first found is used): ENV, CWD, HOME, /etc/ansible ''' path0 = os.getenv("ANSIBLE_CONFIG", None) if path0 is not None: path0 = unfrackpath(path0, follow=False) if os.path.isdir(path0): path0 += "/ansible.cfg" try: path1 = os.getcwd() + "/ansible.cfg" except OSError: path1 = None path2 = unfrackpath("~/.ansible.cfg", follow=False) path3 = "/etc/ansible/ansible.cfg" for path in [path0, path1, path2, path3]: if path is not None and os.path.exists(path): break else: path = None return path def make_boolean(self, value): ret = value if not isinstance(value, bool): if value is None: ret = False ret = (to_text(value).lower() in self.data.BOOL_TRUE) return ret def ensure_type(self, value, value_type): ''' return a configuration variable with casting :arg value: The value to ensure correct typing of :kwarg value_type: The type of the value. This can be any of the following strings: :boolean: sets the value to a True or False value :integer: Sets the value to an integer or raises a ValueType error :float: Sets the value to a float or raises a ValueType error :list: Treats the value as a comma separated list. Split the value and return it as a python list. :none: Sets the value to None :path: Expands any environment variables and tilde's in the value. :tmp_path: Create a unique temporary directory inside of the directory specified by value and return its path. :pathlist: Treat the value as a typical PATH string. (On POSIX, this means colon separated strings.) Split the value and then expand each part for environment variables and tildes. ''' if value_type == 'boolean': value = self.make_boolean(value) elif value: if value_type == 'integer': value = int(value) elif value_type == 'float': value = float(value) elif value_type == 'list': if isinstance(value, string_types): value = [x.strip() for x in value.split(',')] elif value_type == 'none': if value == "None": value = None elif value_type == 'path': value = resolve_path(value) elif value_type == 'tmppath': value = resolve_path(value) if not os.path.exists(value): makedirs_safe(value, 0o700) prefix = 'ansible-local-%s' % os.getpid() value = tempfile.mkdtemp(prefix=prefix, dir=value) elif value_type == 'pathlist': if isinstance(value, string_types): value = [resolve_path(x) for x in value.split(os.pathsep)] elif isinstance(value, string_types): value = unquote(value) return to_text(value, errors='surrogate_or_strict', nonstring='passthru')