diff --git a/changelog/60003.fixed b/changelog/60003.fixed new file mode 100644 index 000000000000..7b0ccd4010c5 --- /dev/null +++ b/changelog/60003.fixed @@ -0,0 +1 @@ +Fix salt-ssh when using imports with extra-filerefs. diff --git a/salt/client/ssh/wrapper/state.py b/salt/client/ssh/wrapper/state.py index 5df4d71166fc..5bfc1ecd049d 100644 --- a/salt/client/ssh/wrapper/state.py +++ b/salt/client/ssh/wrapper/state.py @@ -682,7 +682,7 @@ def highstate(test=None, **kwargs): context=__context__.value(), ) as st_: st_.push_active() - chunks = st_.compile_low_chunks() + chunks = st_.compile_low_chunks(context=__context__.value()) file_refs = salt.client.ssh.state.lowstate_file_refs( chunks, _merge_extra_filerefs( @@ -768,7 +768,7 @@ def top(topfn, test=None, **kwargs): ) as st_: st_.opts["state_top"] = os.path.join("salt://", topfn) st_.push_active() - chunks = st_.compile_low_chunks() + chunks = st_.compile_low_chunks(context=__context__.value()) file_refs = salt.client.ssh.state.lowstate_file_refs( chunks, _merge_extra_filerefs( @@ -840,7 +840,7 @@ def show_highstate(**kwargs): context=__context__.value(), ) as st_: st_.push_active() - chunks = st_.compile_highstate() + chunks = st_.compile_highstate(context=__context__.value()) _cleanup_slsmod_high_data(chunks) return chunks @@ -865,7 +865,7 @@ def show_lowstate(**kwargs): context=__context__.value(), ) as st_: st_.push_active() - chunks = st_.compile_low_chunks() + chunks = st_.compile_low_chunks(context=__context__.value()) _cleanup_slsmod_low_data(chunks) return chunks @@ -932,7 +932,9 @@ def sls_id(id_, mods, test=None, queue=False, **kwargs): split_mods = _parse_mods(mods) st_.push_active() - high_, errors = st_.render_highstate({opts["saltenv"]: split_mods}) + high_, errors = st_.render_highstate( + {opts["saltenv"]: split_mods}, context=__context__.value() + ) errors += st_.state.verify_high(high_) # Apply requisites to high data high_, req_in_errors = st_.state.requisite_in(high_) @@ -988,7 +990,9 @@ def show_sls(mods, saltenv="base", test=None, **kwargs): ) as st_: st_.push_active() mods = _parse_mods(mods) - high_data, errors = st_.render_highstate({saltenv: mods}) + high_data, errors = st_.render_highstate( + {saltenv: mods}, context=__context__.value() + ) high_data, ext_errors = st_.state.reconcile_extend(high_data) errors += ext_errors errors += st_.state.verify_high(high_data) @@ -1015,7 +1019,7 @@ def show_low_sls(mods, saltenv="base", test=None, **kwargs): .. code-block:: bash - salt '*' state.show_sls core,edit.vim dev + salt '*' state.show_low_sls core,edit.vim dev """ __pillar__.update(kwargs.get("pillar", {})) __opts__["grains"] = __grains__.value() @@ -1034,7 +1038,9 @@ def show_low_sls(mods, saltenv="base", test=None, **kwargs): ) as st_: st_.push_active() mods = _parse_mods(mods) - high_data, errors = st_.render_highstate({saltenv: mods}) + high_data, errors = st_.render_highstate( + {saltenv: mods}, context=__context__.value() + ) high_data, ext_errors = st_.state.reconcile_extend(high_data) errors += ext_errors errors += st_.state.verify_high(high_data) @@ -1070,7 +1076,7 @@ def show_top(**kwargs): __context__["fileclient"], context=__context__.value(), ) as st_: - top_data = st_.get_top() + top_data = st_.get_top(context=__context__.value()) errors = [] errors += st_.verify_tops(top_data) if errors: diff --git a/salt/state.py b/salt/state.py index b7272bb5928f..60f53af252d6 100644 --- a/salt/state.py +++ b/salt/state.py @@ -3775,7 +3775,7 @@ def _get_envs(self): envs.extend([env for env in client_envs if env not in envs]) return envs - def get_tops(self): + def get_tops(self, context=None): """ Gather the top files """ @@ -3806,6 +3806,7 @@ def get_tops(self): self.state.opts["renderer_blacklist"], self.state.opts["renderer_whitelist"], saltenv=self.opts["saltenv"], + context=context, ) ] else: @@ -3831,6 +3832,7 @@ def get_tops(self): self.state.opts["renderer_blacklist"], self.state.opts["renderer_whitelist"], saltenv=saltenv, + context=context, ) ) else: @@ -3887,6 +3889,7 @@ def get_tops(self): self.state.opts["renderer_blacklist"], self.state.opts["renderer_whitelist"], saltenv, + context=context, ) ) done[saltenv].append(sls) @@ -4137,12 +4140,12 @@ def verify_tops(self, tops): return errors - def get_top(self): + def get_top(self, context=None): """ Returns the high data derived from the top file """ try: - tops = self.get_tops() + tops = self.get_tops(context=context) except SaltRenderError as err: log.error("Unable to render top file: %s", err.error) return {} @@ -4379,7 +4382,11 @@ def render_state(self, sls, saltenv, mods, matches, local=False, context=None): mod_tgt = "{}:{}".format(r_env, sls_target) if mod_tgt not in mods: nstate, err = self.render_state( - sls_target, r_env, mods, matches + sls_target, + r_env, + mods, + matches, + context=context, ) if nstate: self.merge_included_states(state, nstate, errors) @@ -4761,15 +4768,15 @@ def call_highstate( return self.state.call_high(high, orchestration_jid) - def compile_highstate(self): + def compile_highstate(self, context=None): """ Return just the highstate or the errors """ err = [] - top = self.get_top() + top = self.get_top(context=context) err += self.verify_tops(top) matches = self.top_matches(top) - high, errors = self.render_highstate(matches) + high, errors = self.render_highstate(matches, context=context) err += errors if err: @@ -4777,14 +4784,14 @@ def compile_highstate(self): return high - def compile_low_chunks(self): + def compile_low_chunks(self, context=None): """ Compile the highstate but don't run it, return the low chunks to see exactly what the highstate will execute """ - top = self.get_top() + top = self.get_top(context=context) matches = self.top_matches(top) - high, errors = self.render_highstate(matches) + high, errors = self.render_highstate(matches, context=context) # If there is extension data reconcile it high, ext_errors = self.state.reconcile_extend(high) diff --git a/tests/pytests/integration/ssh/test_state.py b/tests/pytests/integration/ssh/test_state.py index c4e7a2f53942..9d3a38d2c9f5 100644 --- a/tests/pytests/integration/ssh/test_state.py +++ b/tests/pytests/integration/ssh/test_state.py @@ -10,6 +10,7 @@ @pytest.fixture(scope="module") def state_tree(base_env_state_tree_root_dir): top_file = """ + {%- from "map.jinja" import abc with context %} base: 'localhost': - basic @@ -21,7 +22,6 @@ def state_tree(base_env_state_tree_root_dir): """ state_file = """ {%- from "map.jinja" import abc with context %} - Ok with {{ abc }}: test.succeed_without_changes """ @@ -34,6 +34,42 @@ def state_tree(base_env_state_tree_root_dir): state_tempfile = pytest.helpers.temp_file( "test.sls", state_file, base_env_state_tree_root_dir ) + with top_tempfile, map_tempfile, state_tempfile: + yield + + +@pytest.fixture(scope="module") +def state_tree_dir(base_env_state_tree_root_dir): + """ + State tree with files to test salt-ssh + when the map.jinja file is in another directory + """ + top_file = """ + {%- from "test/map.jinja" import abc with context %} + base: + 'localhost': + - test + '127.0.0.1': + - test + """ + map_file = """ + {%- set abc = "def" %} + """ + state_file = """ + {%- from "test/map.jinja" import abc with context %} + + Ok with {{ abc }}: + test.succeed_without_changes + """ + top_tempfile = pytest.helpers.temp_file( + "top.sls", top_file, base_env_state_tree_root_dir + ) + map_tempfile = pytest.helpers.temp_file( + "test/map.jinja", map_file, base_env_state_tree_root_dir + ) + state_tempfile = pytest.helpers.temp_file( + "test.sls", state_file, base_env_state_tree_root_dir + ) with top_tempfile, map_tempfile, state_tempfile: yield @@ -49,6 +85,66 @@ def test_state_with_import(salt_ssh_cli, state_tree): assert ret.data +@pytest.mark.parametrize( + "ssh_cmd", + [ + "state.sls", + "state.highstate", + "state.apply", + "state.show_top", + "state.show_highstate", + "state.show_low_sls", + "state.show_lowstate", + "state.sls_id", + "state.show_sls", + "state.top", + ], +) +@pytest.mark.slow_test +def test_state_with_import_dir(salt_ssh_cli, state_tree_dir, ssh_cmd): + """ + verify salt-ssh can use imported map files in states + when the map files are in another directory outside of + sls files importing them. + """ + if ssh_cmd in ("state.sls", "state.show_low_sls", "state.show_sls"): + ret = salt_ssh_cli.run("-w", "-t", ssh_cmd, "test") + elif ssh_cmd == "state.top": + ret = salt_ssh_cli.run("-w", "-t", ssh_cmd, "top.sls") + elif ssh_cmd == "state.sls_id": + ret = salt_ssh_cli.run("-w", "-t", ssh_cmd, "Ok with def", "test") + else: + ret = salt_ssh_cli.run("-w", "-t", ssh_cmd) + assert ret.returncode == 0 + if ssh_cmd == "state.show_top": + assert ret.data == {"base": ["test", "master_tops_test"]} or {"base": ["test"]} + elif ssh_cmd in ("state.show_highstate", "state.show_sls"): + assert ret.data == { + "Ok with def": { + "__sls__": "test", + "__env__": "base", + "test": ["succeed_without_changes", {"order": 10000}], + } + } + elif ssh_cmd in ("state.show_low_sls", "state.show_lowstate", "state.show_sls"): + assert ret.data == [ + { + "state": "test", + "name": "Ok with def", + "__sls__": "test", + "__env__": "base", + "__id__": "Ok with def", + "order": 10000, + "fun": "succeed_without_changes", + } + ] + else: + assert ret.data["test_|-Ok with def_|-Ok with def_|-succeed_without_changes"][ + "result" + ] + assert ret.data + + @pytest.fixture def nested_state_tree(base_env_state_tree_root_dir, tmp_path): top_file = """