|
|
@ -13,9 +13,9 @@ import stat
|
|
|
|
import tempfile
|
|
|
|
import tempfile
|
|
|
|
import traceback
|
|
|
|
import traceback
|
|
|
|
|
|
|
|
|
|
|
|
from collections import namedtuple
|
|
|
|
|
|
|
|
from collections.abc import Mapping, Sequence
|
|
|
|
from collections.abc import Mapping, Sequence
|
|
|
|
from jinja2.nativetypes import NativeEnvironment
|
|
|
|
|
|
|
|
|
|
|
|
from collections import namedtuple
|
|
|
|
|
|
|
|
|
|
|
|
from ansible.config.data import ConfigData
|
|
|
|
from ansible.config.data import ConfigData
|
|
|
|
from ansible.errors import AnsibleOptionsError, AnsibleError
|
|
|
|
from ansible.errors import AnsibleOptionsError, AnsibleError
|
|
|
@ -302,8 +302,7 @@ class ConfigManager(object):
|
|
|
|
self._parse_config_file()
|
|
|
|
self._parse_config_file()
|
|
|
|
|
|
|
|
|
|
|
|
# update constants
|
|
|
|
# update constants
|
|
|
|
self.update_config_data(self._base_defs, self._config_file)
|
|
|
|
self.update_config_data()
|
|
|
|
self._base_defs['CONFIG_FILE'] = {'default': None, 'type': 'path'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _read_config_yaml_file(self, yml_file):
|
|
|
|
def _read_config_yaml_file(self, yml_file):
|
|
|
|
# TODO: handle relative paths as relative to the directory containing the current playbook instead of CWD
|
|
|
|
# TODO: handle relative paths as relative to the directory containing the current playbook instead of CWD
|
|
|
@ -448,9 +447,6 @@ class ConfigManager(object):
|
|
|
|
# use default config
|
|
|
|
# use default config
|
|
|
|
cfile = self._config_file
|
|
|
|
cfile = self._config_file
|
|
|
|
|
|
|
|
|
|
|
|
if config == 'CONFIG_FILE':
|
|
|
|
|
|
|
|
return cfile, ''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Note: sources that are lists listed in low to high precedence (last one wins)
|
|
|
|
# Note: sources that are lists listed in low to high precedence (last one wins)
|
|
|
|
value = None
|
|
|
|
value = None
|
|
|
|
origin = None
|
|
|
|
origin = None
|
|
|
@ -461,94 +457,90 @@ class ConfigManager(object):
|
|
|
|
aliases = defs[config].get('aliases', [])
|
|
|
|
aliases = defs[config].get('aliases', [])
|
|
|
|
|
|
|
|
|
|
|
|
# direct setting via plugin arguments, can set to None so we bypass rest of processing/defaults
|
|
|
|
# direct setting via plugin arguments, can set to None so we bypass rest of processing/defaults
|
|
|
|
|
|
|
|
direct_aliases = []
|
|
|
|
if direct:
|
|
|
|
if direct:
|
|
|
|
if config in direct:
|
|
|
|
direct_aliases = [direct[alias] for alias in aliases if alias in direct]
|
|
|
|
value = direct[config]
|
|
|
|
if direct and config in direct:
|
|
|
|
origin = 'Direct'
|
|
|
|
value = direct[config]
|
|
|
|
else:
|
|
|
|
origin = 'Direct'
|
|
|
|
direct_aliases = [direct[alias] for alias in aliases if alias in direct]
|
|
|
|
elif direct and direct_aliases:
|
|
|
|
if direct_aliases:
|
|
|
|
value = direct_aliases[0]
|
|
|
|
value = direct_aliases[0]
|
|
|
|
origin = 'Direct'
|
|
|
|
origin = 'Direct'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if value is None and variables and defs[config].get('vars'):
|
|
|
|
else:
|
|
|
|
# Use 'variable overrides' if present, highest precedence, but only present when querying running play
|
|
|
|
# Use 'variable overrides' if present, highest precedence, but only present when querying running play
|
|
|
|
value, origin = self._loop_entries(variables, defs[config]['vars'])
|
|
|
|
if variables and defs[config].get('vars'):
|
|
|
|
origin = 'var: %s' % origin
|
|
|
|
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 defs[config].get('keyword') and keys:
|
|
|
|
# use playbook keywords if you have em
|
|
|
|
value, origin = self._loop_entries(keys, defs[config]['keyword'])
|
|
|
|
if value is None and defs[config].get('keyword') and keys:
|
|
|
|
origin = 'keyword: %s' % origin
|
|
|
|
value, origin = self._loop_entries(keys, defs[config]['keyword'])
|
|
|
|
|
|
|
|
origin = 'keyword: %s' % origin
|
|
|
|
# automap to keywords
|
|
|
|
|
|
|
|
# TODO: deprecate these in favor of explicit keyword above
|
|
|
|
# automap to keywords
|
|
|
|
if value is None and keys:
|
|
|
|
# TODO: deprecate these in favor of explicit keyword above
|
|
|
|
if config in keys:
|
|
|
|
if value is None and keys:
|
|
|
|
value = keys[config]
|
|
|
|
if config in keys:
|
|
|
|
keyword = config
|
|
|
|
value = keys[config]
|
|
|
|
|
|
|
|
keyword = config
|
|
|
|
elif aliases:
|
|
|
|
|
|
|
|
for alias in aliases:
|
|
|
|
elif aliases:
|
|
|
|
if alias in keys:
|
|
|
|
for alias in aliases:
|
|
|
|
value = keys[alias]
|
|
|
|
if alias in keys:
|
|
|
|
keyword = alias
|
|
|
|
value = keys[alias]
|
|
|
|
break
|
|
|
|
keyword = alias
|
|
|
|
|
|
|
|
break
|
|
|
|
if value is not None:
|
|
|
|
|
|
|
|
origin = 'keyword: %s' % keyword
|
|
|
|
if value is not None:
|
|
|
|
|
|
|
|
origin = 'keyword: %s' % keyword
|
|
|
|
if value is None and 'cli' in defs[config]:
|
|
|
|
|
|
|
|
# avoid circular import .. until valid
|
|
|
|
if value is None and 'cli' in defs[config]:
|
|
|
|
from ansible import context
|
|
|
|
# avoid circular import .. until valid
|
|
|
|
value, origin = self._loop_entries(context.CLIARGS, defs[config]['cli'])
|
|
|
|
from ansible import context
|
|
|
|
origin = 'cli: %s' % origin
|
|
|
|
value, origin = self._loop_entries(context.CLIARGS, defs[config]['cli'])
|
|
|
|
|
|
|
|
origin = 'cli: %s' % origin
|
|
|
|
# env vars are next precedence
|
|
|
|
|
|
|
|
if value is None and defs[config].get('env'):
|
|
|
|
# env vars are next precedence
|
|
|
|
value, origin = self._loop_entries(py3compat.environ, defs[config]['env'])
|
|
|
|
if value is None and defs[config].get('env'):
|
|
|
|
origin = 'env: %s' % origin
|
|
|
|
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:
|
|
|
|
# try config file entries next, if we have one
|
|
|
|
self._parse_config_file(cfile)
|
|
|
|
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 value is None and cfile is not None:
|
|
|
|
if ftype and defs[config].get(ftype):
|
|
|
|
ftype = get_config_type(cfile)
|
|
|
|
if ftype == 'ini':
|
|
|
|
if ftype and defs[config].get(ftype):
|
|
|
|
# load from ini config
|
|
|
|
if ftype == 'ini':
|
|
|
|
try: # FIXME: generalize _loop_entries to allow for files also, most of this code is dupe
|
|
|
|
# load from ini config
|
|
|
|
for ini_entry in defs[config]['ini']:
|
|
|
|
try: # FIXME: generalize _loop_entries to allow for files also, most of this code is dupe
|
|
|
|
temp_value = get_ini_config_value(self._parsers[cfile], ini_entry)
|
|
|
|
for ini_entry in defs[config]['ini']:
|
|
|
|
if temp_value is not None:
|
|
|
|
temp_value = get_ini_config_value(self._parsers[cfile], ini_entry)
|
|
|
|
value = temp_value
|
|
|
|
if temp_value is not None:
|
|
|
|
origin = cfile
|
|
|
|
value = temp_value
|
|
|
|
if 'deprecated' in ini_entry:
|
|
|
|
origin = cfile
|
|
|
|
self.DEPRECATED.append(('[%s]%s' % (ini_entry['section'], ini_entry['key']), ini_entry['deprecated']))
|
|
|
|
if 'deprecated' in ini_entry:
|
|
|
|
except Exception as e:
|
|
|
|
self.DEPRECATED.append(('[%s]%s' % (ini_entry['section'], ini_entry['key']), ini_entry['deprecated']))
|
|
|
|
sys.stderr.write("Error while loading ini config %s: %s" % (cfile, to_native(e)))
|
|
|
|
except Exception as e:
|
|
|
|
elif ftype == 'yaml':
|
|
|
|
sys.stderr.write("Error while loading ini config %s: %s" % (cfile, to_native(e)))
|
|
|
|
# FIXME: implement, also , break down key from defs (. notation???)
|
|
|
|
elif ftype == 'yaml':
|
|
|
|
origin = cfile
|
|
|
|
# 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:
|
|
|
|
# set default if we got here w/o a value
|
|
|
|
if defs[config].get('required', False):
|
|
|
|
if value is None:
|
|
|
|
if not plugin_type or config not in INTERNAL_DEFS.get(plugin_type, {}):
|
|
|
|
if defs[config].get('required', False):
|
|
|
|
raise AnsibleError("No setting was provided for required configuration %s" %
|
|
|
|
if not plugin_type or config not in INTERNAL_DEFS.get(plugin_type, {}):
|
|
|
|
to_native(_get_entry(plugin_type, plugin_name, config)))
|
|
|
|
raise AnsibleError("No setting was provided for required configuration %s" %
|
|
|
|
else:
|
|
|
|
to_native(_get_entry(plugin_type, plugin_name, config)))
|
|
|
|
origin = 'default'
|
|
|
|
else:
|
|
|
|
value = defs[config].get('default')
|
|
|
|
value = defs[config].get('default')
|
|
|
|
if isinstance(value, string_types) and (value.startswith('{{') and value.endswith('}}')) and variables is not None:
|
|
|
|
origin = 'default'
|
|
|
|
# template default values if possible
|
|
|
|
# skip typing as this is a templated default that will be resolved later in constants, which has needed vars
|
|
|
|
# NOTE: cannot use is_template due to circular dep
|
|
|
|
if plugin_type is None and isinstance(value, string_types) and (value.startswith('{{') and value.endswith('}}')):
|
|
|
|
try:
|
|
|
|
return value, origin
|
|
|
|
t = NativeEnvironment().from_string(value)
|
|
|
|
|
|
|
|
value = t.render(variables)
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
|
|
pass # not templatable
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ensure correct type, can raise exceptions on mismatched types
|
|
|
|
# ensure correct type, can raise exceptions on mismatched types
|
|
|
|
try:
|
|
|
|
try:
|
|
|
@ -606,16 +598,19 @@ class ConfigManager(object):
|
|
|
|
|
|
|
|
|
|
|
|
if defs is None:
|
|
|
|
if defs is None:
|
|
|
|
defs = self._base_defs
|
|
|
|
defs = self._base_defs
|
|
|
|
|
|
|
|
|
|
|
|
if configfile is None:
|
|
|
|
if configfile is None:
|
|
|
|
configfile = self._config_file
|
|
|
|
configfile = self._config_file
|
|
|
|
|
|
|
|
|
|
|
|
if not isinstance(defs, dict):
|
|
|
|
if not isinstance(defs, dict):
|
|
|
|
raise AnsibleOptionsError("Invalid configuration definition type: %s for %s" % (type(defs), defs))
|
|
|
|
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
|
|
|
|
origin = None
|
|
|
|
# env and config defs can have several entries, ordered in list from lowest to highest precedence
|
|
|
|
# env and config defs can have several entries, ordered in list from lowest to highest precedence
|
|
|
|
for config in defs:
|
|
|
|
for config in defs:
|
|
|
|
|
|
|
|
|
|
|
|
if not isinstance(defs[config], dict):
|
|
|
|
if not isinstance(defs[config], dict):
|
|
|
|
raise AnsibleOptionsError("Invalid configuration definition '%s': type is %s" % (to_native(config), type(defs[config])))
|
|
|
|
raise AnsibleOptionsError("Invalid configuration definition '%s': type is %s" % (to_native(config), type(defs[config])))
|
|
|
|
|
|
|
|
|
|
|
@ -637,6 +632,3 @@ class ConfigManager(object):
|
|
|
|
|
|
|
|
|
|
|
|
# set the constant
|
|
|
|
# set the constant
|
|
|
|
self.data.update_setting(Setting(config, value, origin, defs[config].get('type', 'string')))
|
|
|
|
self.data.update_setting(Setting(config, value, origin, defs[config].get('type', 'string')))
|
|
|
|
|
|
|
|
|
|
|
|
# update the constant for config file
|
|
|
|
|
|
|
|
self.data.update_setting(Setting('CONFIG_FILE', configfile, '', 'string'))
|
|
|
|
|
|
|
|