Revert "Config, ensure templating happens at functions (#77483)"

This reverts commit 6e5f1d781d.
pull/77555/head
Matt Clay 3 years ago
parent 789d29e895
commit 94c9106153

@ -1,2 +0,0 @@
bugfixes:
- config, ensure that pulling values from configmanager are templated if possible.

@ -35,13 +35,6 @@ from ansible.utils.path import unfrackpath
display = Display() display = Display()
def get_constants():
''' helper method to ensure we can template based on existing constants '''
if not hasattr(get_constants, 'cvars'):
get_constants.cvars = {k: getattr(C, k) for k in dir(C) if not k.startswith('__')}
return get_constants.cvars
class ConfigCLI(CLI): class ConfigCLI(CLI):
""" Config command line class """ """ Config command line class """
@ -402,9 +395,9 @@ class ConfigCLI(CLI):
def _get_global_configs(self): def _get_global_configs(self):
config = self.config.get_configuration_definitions(ignore_private=True).copy() config = self.config.get_configuration_definitions(ignore_private=True).copy()
for setting in config.keys(): for setting in self.config.data.get_settings():
v, o = C.config.get_config_value_and_origin(setting, cfile=self.config_file, variables=get_constants()) if setting.name in config:
config[setting] = Setting(setting, v, o, None) config[setting.name] = setting
return self._render_settings(config) return self._render_settings(config)
@ -452,7 +445,7 @@ class ConfigCLI(CLI):
# actually get the values # actually get the values
for setting in config_entries[finalname].keys(): for setting in config_entries[finalname].keys():
try: try:
v, o = C.config.get_config_value_and_origin(setting, cfile=self.config_file, plugin_type=ptype, plugin_name=name, variables=get_constants()) v, o = C.config.get_config_value_and_origin(setting, plugin_type=ptype, plugin_name=name)
except AnsibleError as e: except AnsibleError as e:
if to_text(e).startswith('No setting was provided for required configuration'): if to_text(e).startswith('No setting was provided for required configuration'):
v = None v = None

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

@ -7,9 +7,11 @@ __metaclass__ = type
import re import re
from ast import literal_eval
from jinja2 import Template
from string import ascii_letters, digits from string import ascii_letters, digits
from ansible.config.manager import ConfigManager from ansible.config.manager import ConfigManager, ensure_type
from ansible.module_utils._text import to_text from ansible.module_utils._text import to_text
from ansible.module_utils.common.collections import Sequence from ansible.module_utils.common.collections import Sequence
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
@ -179,8 +181,25 @@ MAGIC_VARIABLE_MAPPING = dict(
config = ConfigManager() config = ConfigManager()
# Generate constants from config # Generate constants from config
for setting in config.get_configuration_definitions(): for setting in config.data.get_settings():
set_constant(setting, config.get_config_value(setting, variables=vars()))
value = setting.value
if setting.origin == 'default' and \
isinstance(setting.value, string_types) and \
(setting.value.startswith('{{') and setting.value.endswith('}}')):
try:
t = Template(setting.value)
value = t.render(vars())
try:
value = literal_eval(value)
except ValueError:
pass # not a python data structure
except Exception:
pass # not templatable
value = ensure_type(value, setting.type)
set_constant(setting.name, value)
for warn in config.WARNINGS: for warn in config.WARNINGS:
_warning(warn) _warning(warn)

Loading…
Cancel
Save