diff --git a/conf/minion b/conf/minion index 3950877d3e25..ed951cd7a0fb 100644 --- a/conf/minion +++ b/conf/minion @@ -565,6 +565,15 @@ # #state_aggregate: False +# Disable requisites during state runs by specifying a single requisite +# or a list of requisites to disable. +# +# disabled_requisites: require_in +# +# disabled_requisites: +# - require +# - require_in + ##### File Directory Settings ##### ########################################## # The Salt Minion can redirect all file server operations to a local directory, diff --git a/doc/topics/releases/sodium.rst b/doc/topics/releases/sodium.rst index d8fc2e454ee1..f7e065265a48 100644 --- a/doc/topics/releases/sodium.rst +++ b/doc/topics/releases/sodium.rst @@ -87,3 +87,9 @@ You can set this setting in your roster file like so: user: root passwd: P@ssword set_path: '$PATH:/usr/local/bin/' + + +State Changes +============= +- Adding a new option for the State compiler, ``disabled_requisites`` will allow + requisites to be disabled during State runs. diff --git a/salt/config/__init__.py b/salt/config/__init__.py index 92508ef78f62..ac4d2ab5c281 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py @@ -925,6 +925,7 @@ def _gather_buffer_space(): # Allow raw_shell option when using the ssh # client via the Salt API "netapi_allow_raw_shell": bool, + "disabled_requisites": (six.string_types, list), } ) @@ -1216,6 +1217,7 @@ def _gather_buffer_space(): "discovery": False, "schedule": {}, "ssh_merge_pillar": True, + "disabled_requisites": [], } ) diff --git a/salt/state.py b/salt/state.py index 3138f76f4cf1..995e434fbf06 100644 --- a/salt/state.py +++ b/salt/state.py @@ -1684,6 +1684,9 @@ def requisite_in(self, high): ) extend = {} errors = [] + disabled_reqs = self.opts.get("disabled_requisites", []) + if not isinstance(disabled_reqs, list): + disabled_reqs = [disabled_reqs] for id_, body in six.iteritems(high): if not isinstance(body, dict): continue @@ -1702,6 +1705,11 @@ def requisite_in(self, high): key = next(iter(arg)) if key not in req_in: continue + if key in disabled_reqs: + log.warning( + "The %s requisite has been disabled, Ignoring.", key + ) + continue rkey = key.split("_")[0] items = arg[key] if isinstance(items, dict): @@ -2543,6 +2551,9 @@ def check_requisite(self, low, running, chunks, pre=False): Look into the running data to check the status of all requisite states """ + disabled_reqs = self.opts.get("disabled_requisites", []) + if not isinstance(disabled_reqs, list): + disabled_reqs = [disabled_reqs] present = False # If mod_watch is not available make it a require if "watch" in low: @@ -2598,6 +2609,11 @@ def check_requisite(self, low, running, chunks, pre=False): reqs["prerequired"] = [] for r_state in reqs: if r_state in low and low[r_state] is not None: + if r_state in disabled_reqs: + log.warning( + "The %s requisite has been disabled, Ignoring.", r_state + ) + continue for req in low[r_state]: if isinstance(req, six.string_types): req = {"id": req} diff --git a/tests/unit/test_state.py b/tests/unit/test_state.py index 6a3de4b85346..1d135c991bef 100644 --- a/tests/unit/test_state.py +++ b/tests/unit/test_state.py @@ -274,6 +274,89 @@ def test_verify_retry_parsing(self): with patch.object(state_obj, "_run_check", return_value=mock): self.assertDictContainsSubset(expected_result, state_obj.call(low_data)) + def test_render_requisite_require_disabled(self): + """ + Test that the state compiler correctly deliver a rendering + exception when a requisite cannot be resolved + """ + with patch("salt.state.State._gather_pillar") as state_patch: + high_data = { + "step_one": OrderedDict( + [ + ( + "test", + [ + OrderedDict( + [("require", [OrderedDict([("test", "step_two")])])] + ), + "succeed_with_changes", + {"order": 10000}, + ], + ), + ("__sls__", "test.disable_require"), + ("__env__", "base"), + ] + ), + "step_two": { + "test": ["succeed_with_changes", {"order": 10001}], + "__env__": "base", + "__sls__": "test.disable_require", + }, + } + + minion_opts = self.get_temp_config("minion") + minion_opts["disabled_requisites"] = ["require"] + state_obj = salt.state.State(minion_opts) + ret = state_obj.call_high(high_data) + run_num = ret["test_|-step_one_|-step_one_|-succeed_with_changes"][ + "__run_num__" + ] + self.assertEqual(run_num, 0) + + def test_render_requisite_require_in_disabled(self): + """ + Test that the state compiler correctly deliver a rendering + exception when a requisite cannot be resolved + """ + with patch("salt.state.State._gather_pillar") as state_patch: + high_data = { + "step_one": { + "test": ["succeed_with_changes", {"order": 10000}], + "__env__": "base", + "__sls__": "test.disable_require_in", + }, + "step_two": OrderedDict( + [ + ( + "test", + [ + OrderedDict( + [ + ( + "require_in", + [OrderedDict([("test", "step_one")])], + ) + ] + ), + "succeed_with_changes", + {"order": 10001}, + ], + ), + ("__sls__", "test.disable_require_in"), + ("__env__", "base"), + ] + ), + } + + minion_opts = self.get_temp_config("minion") + minion_opts["disabled_requisites"] = ["require_in"] + state_obj = salt.state.State(minion_opts) + ret = state_obj.call_high(high_data) + run_num = ret["test_|-step_one_|-step_one_|-succeed_with_changes"][ + "__run_num__" + ] + self.assertEqual(run_num, 0) + class HighStateTestCase(TestCase, AdaptedConfigurationTestCaseMixin): def setUp(self):