diff --git a/salt/config/__init__.py b/salt/config/__init__.py index b821eb54832e..25eb8948893c 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py @@ -944,6 +944,8 @@ def _gather_buffer_space(): # client via the Salt API "netapi_allow_raw_shell": bool, "disabled_requisites": (str, list), + # Feature flag config + "features": dict, } ) diff --git a/salt/features.py b/salt/features.py new file mode 100644 index 000000000000..41601255e2d3 --- /dev/null +++ b/salt/features.py @@ -0,0 +1,27 @@ +import logging + + +log = logging.getLogger(__name__) + + +class Features(object): + + def __init__(self, _features=None): + if _features is None: + self.features = {} + else: + self.features = _features + self.setup = False + + def setup_features(self, opts): + if not self.setup: + self.features.update(opts.get('features', {})) + else: + log.warn("Features already setup") + + def get(self, key, default=None): + return self.features.get(key, default) + + +features = Features() +setup_features = features.setup_features diff --git a/salt/utils/parsers.py b/salt/utils/parsers.py index af3007556554..ec0fdfc00f5b 100644 --- a/salt/utils/parsers.py +++ b/salt/utils/parsers.py @@ -42,6 +42,7 @@ import salt.utils.xdg import salt.utils.yaml import salt.version as version +import salt.features from salt.defaults import DEFAULT_TARGET_DELIM from salt.ext import six from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin @@ -1960,7 +1961,9 @@ class MasterOptionParser( _setup_mp_logging_listener_ = True def setup_config(self): - return config.master_config(self.get_config_file_path()) + opts = config.master_config(self.get_config_file_path()) + salt.features.setup_features(opts) + return opts class MinionOptionParser( @@ -1989,6 +1992,7 @@ def setup_config(self): and self.options.daemon ): # pylint: disable=no-member self._setup_mp_logging_listener_ = False + salt.features.setup_features(opts) return opts @@ -2022,9 +2026,11 @@ def setup_config(self): except AttributeError: minion_id = None - return config.proxy_config( + opts = config.proxy_config( self.get_config_file_path(), cache_minion_id=False, minion_id=minion_id ) + salt.features.setup_features(opts) + return opts class SyndicOptionParser( @@ -2056,9 +2062,11 @@ class SyndicOptionParser( _setup_mp_logging_listener_ = True def setup_config(self): - return config.syndic_config( + opts = config.syndic_config( self.get_config_file_path(), self.get_config_file_path("minion") ) + salt.features.setup_features(opts) + return opts class SaltCMDOptionParser( @@ -2398,7 +2406,9 @@ def _mixin_after_parsed(self): self.exit(42, "\nIncomplete options passed.\n\n") def setup_config(self): - return config.client_config(self.get_config_file_path()) + opts = config.client_config(self.get_config_file_path()) + salt.features.setup_features(opts) + return opts class SaltCPOptionParser( @@ -2469,7 +2479,9 @@ def _mixin_after_parsed(self): self.config["dest"] = self.args[-1] def setup_config(self): - return config.master_config(self.get_config_file_path()) + opts = config.master_config(self.get_config_file_path()) + salt.features.setup_features(opts) + return opts class SaltKeyOptionParser( @@ -2758,7 +2770,7 @@ def setup_config(self): # or tweaked keys_config[self._logfile_config_setting_name_] = os.devnull keys_config["pki_dir"] = self.options.gen_keys_dir - + salt.features.setup_features(keys_config) return keys_config def process_rotate_aes_key(self): @@ -3009,6 +3021,7 @@ def setup_config(self): opts = config.minion_config( self.get_config_file_path(), cache_minion_id=True ) + salt.features.setup_features(opts) return opts def process_module_dirs(self): @@ -3111,7 +3124,10 @@ def _mixin_after_parsed(self): self.config["arg"] = [] def setup_config(self): - return config.client_config(self.get_config_file_path()) + opts = config.client_config(self.get_config_file_path()) + salt.features.setup_features(opts) + return opts + class SaltSSHOptionParser( @@ -3437,7 +3453,9 @@ def _mixin_after_parsed(self): break def setup_config(self): - return config.master_config(self.get_config_file_path()) + opts = config.master_config(self.get_config_file_path()) + salt.features.setup_features(opts) + return opts def process_jid(self): if self.options.jid is not None: @@ -3505,9 +3523,11 @@ def _mixin_after_parsed(self): def setup_config(self): try: - return config.cloud_config(self.get_config_file_path()) + opts = config.cloud_config(self.get_config_file_path()) except salt.exceptions.SaltCloudConfigError as exc: self.error(exc) + salt.features.setup_features(opts) + return opts class SPMParser( @@ -3565,7 +3585,9 @@ def _mixin_after_parsed(self): self.error("Insufficient arguments") def setup_config(self): - return salt.config.spm_config(self.get_config_file_path()) + opts = salt.config.spm_config(self.get_config_file_path()) + salt.features.setup_features(opts) + return opts class SaltAPIParser( @@ -3593,6 +3615,8 @@ class SaltAPIParser( _default_logging_logfile_ = config.DEFAULT_API_OPTS[_logfile_config_setting_name_] def setup_config(self): - return salt.config.api_config( + opts = salt.config.api_config( self.get_config_file_path() ) # pylint: disable=no-member + salt.features.setup_features(opts) + return opts diff --git a/salt/utils/templates.py b/salt/utils/templates.py index dc61f838415a..eb9e8c230717 100644 --- a/salt/utils/templates.py +++ b/salt/utils/templates.py @@ -23,6 +23,7 @@ import salt.utils.platform import salt.utils.stringutils import salt.utils.yamlencoding +from salt.features import features from salt import __path__ as saltpath from salt.exceptions import CommandExecutionError, SaltInvocationError, SaltRenderError from salt.ext import six @@ -93,7 +94,42 @@ def __getattr__(self, name): return getattr(self.wrapped, name) -def generate_sls_context(tmplpath, sls): +def _generate_sls_context_legacy(tmplpath, sls): + """ + Legacy version of generate_sls_context, this method should be remove in the + Phosphorus release. + """ + salt.utils.versions.warn_until( + "Phosphorus", + "There have been significant improvement to template variables. " + "To enable these improvements set features.enable_slsvars_fixes " + "to True in your config file. This feature will become the default " + "in the Phoshorus release." + ) + context = {} + slspath = sls.replace(".", "/") + if tmplpath is not None: + context["tplpath"] = tmplpath + if not tmplpath.lower().replace("\\", "/").endswith("/init.sls"): + slspath = os.path.dirname(slspath) + template = tmplpath.replace("\\", "/") + i = template.rfind(slspath.replace(".", "/")) + if i != -1: + template = template[i:] + tpldir = os.path.dirname(template).replace("\\", "/") + tpldata = { + "tplfile": template, + "tpldir": "." if tpldir == "" else tpldir, + "tpldot": tpldir.replace("/", "."), + } + context.update(tpldata) + context["slsdotpath"] = slspath.replace("/", ".") + context["slscolonpath"] = slspath.replace("/", ":") + context["sls_path"] = slspath.replace("/", "_") + context["slspath"] = slspath + return context + +def _generate_sls_context(tmplpath, sls): """ Generate SLS/Template Context Items @@ -154,6 +190,27 @@ def generate_sls_context(tmplpath, sls): return sls_context +def generate_sls_context(tmplpath, sls): + """ + Generate SLS/Template Context Items + + Return values: + + tplpath - full path to template on filesystem including filename + tplfile - relative path to template -- relative to file roots + tpldir - directory of the template relative to file roots. If none, "." + tpldot - tpldir using dots instead of slashes, if none, "" + slspath - directory containing current sls - (same as tpldir), if none, "" + sls_path - slspath with underscores separating parts, if none, "" + slsdotpath - slspath with dots separating parts, if none, "" + slscolonpath- slspath with colons separating parts, if none, "" + + """ + if not features.get('enable_slsvars_fixes', False): + return _generate_sls_context_legacy(tmplpath, sls) + _generate_sls_context(tmplpath, sls) + + def wrap_tmpl_func(render_str): def render_tmpl( tmplsrc, from_str=False, to_str=False, context=None, tmplpath=None, **kws diff --git a/tests/unit/utils/test_templates.py b/tests/unit/utils/test_templates.py index 8a152923d96c..9ba03509c904 100644 --- a/tests/unit/utils/test_templates.py +++ b/tests/unit/utils/test_templates.py @@ -224,7 +224,7 @@ def _test_generated_sls_context(self, tmplpath, sls, **expected): if tmplpath.startswith("\\"): tmplpath = "C:{}".format(tmplpath) expected["tplpath"] = tmplpath - actual = salt.utils.templates.generate_sls_context(tmplpath, sls) + actual = salt.utils.templates._generate_sls_context(tmplpath, sls) self.assertDictContainsAll(actual, **expected) @mock.patch("salt.utils.templates.generate_sls_context")