From 8b6354d348b33287864e66b7b2b3a082fe4d4a5c Mon Sep 17 00:00:00 2001 From: Matt Phillips Date: Mon, 4 Feb 2019 20:08:51 -0500 Subject: [PATCH] address circular reference in LazyLoader with ThreadLocalProxy when lazyLoader was getting instantiated from inside already lazyloaded modules (ie state), pillar/grain (and probably other) globals were already populated. In this case they weren't getting dereferenced properly when wrapped via the NamespacedDict wrapper, causing an infinite loop. This should fix that scenario. Fixes https://github.com/saltstack/salt/pull/50655#issuecomment-460050683 --- salt/loader.py | 24 +++++++++++++++---- .../files/file/base/issue-51499.sls | 6 +++++ tests/integration/modules/test_state.py | 13 ++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 tests/integration/files/file/base/issue-51499.sls diff --git a/salt/loader.py b/salt/loader.py index 589d0aa16a37..650e6775eef2 100644 --- a/salt/loader.py +++ b/salt/loader.py @@ -35,12 +35,12 @@ import salt.utils.lazy import salt.utils.odict import salt.utils.platform -import salt.utils.thread_local_proxy import salt.utils.versions import salt.utils.stringutils from salt.exceptions import LoaderError from salt.template import check_render_pipe_str from salt.utils.decorators import Depends +from salt.utils.thread_local_proxy import ThreadLocalProxy # Import 3rd-party libs from salt.ext import six @@ -1118,7 +1118,6 @@ def _inject_into_mod(mod, name, value, force_lock=False): module's variable without acquiring the lock and only acquires the lock if a new proxy has to be created and injected. ''' - from salt.utils.thread_local_proxy import ThreadLocalProxy old_value = getattr(mod, name, None) # We use a double-checked locking scheme in order to avoid taking the lock # when a proxy object has already been injected. @@ -1248,7 +1247,12 @@ def __init__(self, for k, v in six.iteritems(self.pack): if v is None: # if the value of a pack is None, lets make an empty dict - self.context_dict.setdefault(k, {}) + value = self.context_dict.get(k, {}) + + if isinstance(value, ThreadLocalProxy): + value = ThreadLocalProxy.unproxy(value) + + self.context_dict[k] = value self.pack[k] = salt.utils.context.NamespacedDictWrapper(self.context_dict, k) self.whitelist = whitelist @@ -1526,11 +1530,21 @@ def __prep_mod_opts(self, opts): Strip out of the opts any logger instance ''' if '__grains__' not in self.pack: - self.context_dict['grains'] = opts.get('grains', {}) + grains = opts.get('grains', {}) + + if isinstance(grains, ThreadLocalProxy): + grains = ThreadLocalProxy.unproxy(grains) + + self.context_dict['grains'] = grains self.pack['__grains__'] = salt.utils.context.NamespacedDictWrapper(self.context_dict, 'grains') if '__pillar__' not in self.pack: - self.context_dict['pillar'] = opts.get('pillar', {}) + pillar = opts.get('pillar', {}) + + if isinstance(pillar, ThreadLocalProxy): + pillar = ThreadLocalProxy.unproxy(pillar) + + self.context_dict['pillar'] = pillar self.pack['__pillar__'] = salt.utils.context.NamespacedDictWrapper(self.context_dict, 'pillar') mod_opts = {} diff --git a/tests/integration/files/file/base/issue-51499.sls b/tests/integration/files/file/base/issue-51499.sls new file mode 100644 index 000000000000..0e15e7396eae --- /dev/null +++ b/tests/integration/files/file/base/issue-51499.sls @@ -0,0 +1,6 @@ +{% if 'nonexistent_module.function' in salt %} +{% do salt.log.warning("Module is available") %} +{% endif %} +always-passes: + test.succeed_without_changes: + - name: foo diff --git a/tests/integration/modules/test_state.py b/tests/integration/modules/test_state.py index 77e201d7fad5..150e4c3b940f 100644 --- a/tests/integration/modules/test_state.py +++ b/tests/integration/modules/test_state.py @@ -2171,3 +2171,16 @@ def test_state_sls_integer_name(self): self.assertEqual(state_run[state_id]['comment'], 'Success!') self.assertTrue(state_run[state_id]['result']) + + def test_state_sls_lazyloader_allows_recursion(self): + ''' + This tests that referencing dunders like __salt__ work + context: https://github.com/saltstack/salt/pull/51499 + ''' + state_run = self.run_function('state.sls', mods='issue-51499') + + state_id = 'test_|-always-passes_|-foo_|-succeed_without_changes' + self.assertIn(state_id, state_run) + self.assertEqual(state_run[state_id]['comment'], + 'Success!') + self.assertTrue(state_run[state_id]['result'])