diff --git a/doc/topics/releases/sodium.rst b/doc/topics/releases/sodium.rst index 4bb34f8dd255..d8fc2e454ee1 100644 --- a/doc/topics/releases/sodium.rst +++ b/doc/topics/releases/sodium.rst @@ -17,6 +17,16 @@ The old syntax for the mine_function - as a dict, or as a list with dicts that contain more than exactly one key - is still supported but discouraged in favor of the more uniform syntax of module.run. +State Execution Module +====================== + +The :mod:`state.test ` function +can be used to test a state on a minion. This works by executing the +:mod:`state.apply ` function while forcing the ``test`` kwarg +to ``True`` so that the ``state.apply`` function is not required to be called by the +user directly. This also allows you to add the ``state.test`` function to a minion's +``minion_blackout_whitelist`` pillar if you wish to be able to test a state while a +minion is in blackout. New Grains ========== diff --git a/salt/modules/state.py b/salt/modules/state.py index 4edca2de1d39..05cc49fdcd42 100644 --- a/salt/modules/state.py +++ b/salt/modules/state.py @@ -60,6 +60,7 @@ "template": "highstate", "template_str": "highstate", "apply_": "highstate", + "test": "highstate", "request": "highstate", "check_request": "highstate", "run_request": "highstate", @@ -799,6 +800,21 @@ def apply_(mods=None, **kwargs): return highstate(**kwargs) +def test(*args, **kwargs): + """ + .. versionadded:: Sodium + + Alias for `state.apply` with the kwarg `test` forced to `True`. + + This is a nicety to avoid the need to type out `test=True` and the possibility of + a typo causing changes you do not intend. + """ + kwargs["test"] = True + ret = apply_(*args, **kwargs) + + return ret + + def request(mods=None, **kwargs): """ .. versionadded:: 2015.5.0 diff --git a/tests/integration/modules/test_state.py b/tests/integration/modules/test_state.py index 44301badbe48..733361c92dce 100644 --- a/tests/integration/modules/test_state.py +++ b/tests/integration/modules/test_state.py @@ -2354,6 +2354,37 @@ def test_state_sls_id_test_false_pillar_true(self): self.assertEqual(val["comment"], "File {0} updated".format(file_name)) self.assertEqual(val["changes"]["diff"], "New file") + def test_state_test_pillar_false(self): + """ + test state.test forces test kwarg to True even when pillar is set to False + """ + self._add_runtime_pillar(pillar={"test": False}) + testfile = os.path.join(RUNTIME_VARS.TMP, "testfile") + comment = "The file {0} is set to be changed\nNote: No changes made, actual changes may\nbe different due to other states.".format( + testfile + ) + ret = self.run_function("state.test", ["core"]) + + for key, val in ret.items(): + self.assertEqual(val["comment"], comment) + self.assertEqual(val["changes"], {"newfile": testfile}) + + def test_state_test_test_false_pillar_false(self): + """ + test state.test forces test kwarg to True even when pillar and kwarg are set + to False + """ + self._add_runtime_pillar(pillar={"test": False}) + testfile = os.path.join(RUNTIME_VARS.TMP, "testfile") + comment = "The file {0} is set to be changed\nNote: No changes made, actual changes may\nbe different due to other states.".format( + testfile + ) + ret = self.run_function("state.test", ["core"], test=False) + + for key, val in ret.items(): + self.assertEqual(val["comment"], comment) + self.assertEqual(val["changes"], {"newfile": testfile}) + @skipIf( six.PY3 and salt.utils.platform.is_darwin(), "Test is broken on macosx and PY3" ) diff --git a/tests/unit/modules/test_state.py b/tests/unit/modules/test_state.py index ca0b3e7aadb6..93effa90ea50 100644 --- a/tests/unit/modules/test_state.py +++ b/tests/unit/modules/test_state.py @@ -432,6 +432,23 @@ def test_apply_(self): with patch.object(state, "highstate", mock): self.assertTrue(state.apply_(None)) + def test_test(self): + """ + Test to apply states in test mode + """ + with patch.dict(state.__opts__, {"test": False}): + mock = MagicMock(return_value=True) + with patch.object(state, "sls", mock): + self.assertTrue(state.test(True)) + mock.assert_called_once_with(True, test=True) + self.assertEqual(state.__opts__["test"], False) + + mock = MagicMock(return_value=True) + with patch.object(state, "highstate", mock): + self.assertTrue(state.test(None)) + mock.assert_called_once_with(test=True) + self.assertEqual(state.__opts__["test"], False) + def test_list_disabled(self): """ Test to list disabled states @@ -1194,6 +1211,14 @@ def test_lock_saltenv(self): with self.assertRaisesRegex(CommandExecutionError, lock_msg): state.apply_(saltenv="base") + # Test "test" with SLS + with self.assertRaisesRegex(CommandExecutionError, lock_msg): + state.test("foo", saltenv="base") + + # Test "test" with Highstate + with self.assertRaisesRegex(CommandExecutionError, lock_msg): + state.test(saltenv="base") + # Test highstate with self.assertRaisesRegex(CommandExecutionError, lock_msg): state.highstate(saltenv="base")