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)
Example #2
0
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')))
Example #3
0
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')))
Example #4
0
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')))
Example #5
0
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')))
Example #6
0
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))
Example #7
0
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')