File fix-onlyif-unless-when-multiple-conditions-bsc-11808.patch of Package salt

From 828ca76e2083d87ace12b488277e51d4e30c8c9a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
 <psuarezhernandez@suse.com>
Date: Thu, 21 Jan 2021 11:19:07 +0000
Subject: [PATCH] Fix onlyif/unless when multiple conditions
 (bsc#1180818)

Add unit tests to ensure right onlyif/unless behavior

Add extra unit test to cover valid cases

Add unit tests cases to ensure proper onlyif/unless behavior

Change tests to use 'exit' cmd and work outside Linux
---
 salt/state.py            |  20 ++++--
 tests/unit/test_state.py | 148 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 163 insertions(+), 5 deletions(-)

diff --git a/salt/state.py b/salt/state.py
index cc6db7e1b2..070a914636 100644
--- a/salt/state.py
+++ b/salt/state.py
@@ -947,8 +947,10 @@ class State:
                         "result": True,
                     }
                 )
+                return False
             elif cmd == 0:
                 ret.update({"comment": "onlyif condition is true", "result": False})
+            return True
 
         for entry in low_data_onlyif:
             if isinstance(entry, str):
@@ -960,7 +962,8 @@ class State:
                     # Command failed, notify onlyif to skip running the item
                     cmd = 100
                 log.debug("Last command return code: %s", cmd)
-                _check_cmd(cmd)
+                if not _check_cmd(cmd):
+                    return ret
             elif isinstance(entry, dict):
                 if "fun" not in entry:
                     ret["comment"] = "no `fun` argument in onlyif: {}".format(entry)
@@ -972,7 +975,8 @@ class State:
                 if get_return:
                     result = salt.utils.data.traverse_dict_and_list(result, get_return)
                 if self.state_con.get("retcode", 0):
-                    _check_cmd(self.state_con["retcode"])
+                    if not _check_cmd(self.state_con["retcode"]):
+                        return ret
                 elif not result:
                     ret.update(
                         {
@@ -981,6 +985,7 @@ class State:
                             "result": True,
                         }
                     )
+                    return ret
                 else:
                     ret.update({"comment": "onlyif condition is true", "result": False})
 
@@ -991,6 +996,7 @@ class State:
                         "result": False,
                     }
                 )
+                return ret
         return ret
 
     def _run_check_unless(self, low_data, cmd_opts):
@@ -1013,8 +1019,10 @@ class State:
                         "result": True,
                     }
                 )
+                return False
             elif cmd != 0:
                 ret.update({"comment": "unless condition is false", "result": False})
+            return True
 
         for entry in low_data_unless:
             if isinstance(entry, str):
@@ -1026,7 +1034,8 @@ class State:
                 except CommandExecutionError:
                     # Command failed, so notify unless to skip the item
                     cmd = 0
-                _check_cmd(cmd)
+                if not _check_cmd(cmd):
+                    return ret
             elif isinstance(entry, dict):
                 if "fun" not in entry:
                     ret["comment"] = "no `fun` argument in unless: {}".format(entry)
@@ -1038,7 +1047,8 @@ class State:
                 if get_return:
                     result = salt.utils.data.traverse_dict_and_list(result, get_return)
                 if self.state_con.get("retcode", 0):
-                    _check_cmd(self.state_con["retcode"])
+                    if not _check_cmd(self.state_con["retcode"]):
+                        return ret
                 elif result:
                     ret.update(
                         {
@@ -1047,6 +1057,7 @@ class State:
                             "result": True,
                         }
                     )
+                    return ret
                 else:
                     ret.update(
                         {"comment": "unless condition is false", "result": False}
@@ -1058,6 +1069,7 @@ class State:
                         "result": False,
                     }
                 )
+                return ret
 
         # No reason to stop, return ret
         return ret
diff --git a/tests/unit/test_state.py b/tests/unit/test_state.py
index b1bcf8fe83..95018a9cf3 100644
--- a/tests/unit/test_state.py
+++ b/tests/unit/test_state.py
@@ -205,6 +205,152 @@ class StateCompilerTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
                 )
                 self.assertEqual(expected_result, return_result)
 
+    def test_verify_unless_list_cmd(self):
+        low_data = {
+            "state": "cmd",
+            "name": 'echo "something"',
+            "__sls__": "tests.cmd",
+            "__env__": "base",
+            "__id__": "check unless",
+            "unless": ["exit 0", "exit 1"],
+            "order": 10001,
+            "fun": "run",
+        }
+        expected_result = {
+            "comment": "unless condition is true",
+            "result": True,
+            "skip_watch": True,
+        }
+        with patch("salt.state.State._gather_pillar") as state_patch:
+            minion_opts = self.get_temp_config("minion")
+            state_obj = salt.state.State(minion_opts)
+            return_result = state_obj._run_check_unless(low_data, {})
+            self.assertEqual(expected_result, return_result)
+
+    def test_verify_unless_list_cmd_different_order(self):
+        low_data = {
+            "state": "cmd",
+            "name": 'echo "something"',
+            "__sls__": "tests.cmd",
+            "__env__": "base",
+            "__id__": "check unless",
+            "unless": ["exit 1", "exit 0"],
+            "order": 10001,
+            "fun": "run",
+        }
+        expected_result = {
+            "comment": "unless condition is true",
+            "result": True,
+            "skip_watch": True,
+        }
+        with patch("salt.state.State._gather_pillar") as state_patch:
+            minion_opts = self.get_temp_config("minion")
+            state_obj = salt.state.State(minion_opts)
+            return_result = state_obj._run_check_unless(low_data, {})
+            self.assertEqual(expected_result, return_result)
+
+    def test_verify_onlyif_list_cmd_different_order(self):
+        low_data = {
+            "state": "cmd",
+            "name": 'echo "something"',
+            "__sls__": "tests.cmd",
+            "__env__": "base",
+            "__id__": "check onlyif",
+            "onlyif": ["exit 1", "exit 0"],
+            "order": 10001,
+            "fun": "run",
+        }
+        expected_result = {
+            "comment": "onlyif condition is false",
+            "result": True,
+            "skip_watch": True,
+        }
+        with patch("salt.state.State._gather_pillar") as state_patch:
+            minion_opts = self.get_temp_config("minion")
+            state_obj = salt.state.State(minion_opts)
+            return_result = state_obj._run_check_onlyif(low_data, {})
+            self.assertEqual(expected_result, return_result)
+
+    def test_verify_unless_list_cmd_valid(self):
+        low_data = {
+            "state": "cmd",
+            "name": 'echo "something"',
+            "__sls__": "tests.cmd",
+            "__env__": "base",
+            "__id__": "check unless",
+            "unless": ["exit 1", "exit 1"],
+            "order": 10001,
+            "fun": "run",
+        }
+        expected_result = {"comment": "unless condition is false", "result": False}
+        with patch("salt.state.State._gather_pillar") as state_patch:
+            minion_opts = self.get_temp_config("minion")
+            state_obj = salt.state.State(minion_opts)
+            return_result = state_obj._run_check_unless(low_data, {})
+            self.assertEqual(expected_result, return_result)
+
+    def test_verify_onlyif_list_cmd_valid(self):
+        low_data = {
+            "state": "cmd",
+            "name": 'echo "something"',
+            "__sls__": "tests.cmd",
+            "__env__": "base",
+            "__id__": "check onlyif",
+            "onlyif": ["exit 0", "exit 0"],
+            "order": 10001,
+            "fun": "run",
+        }
+        expected_result = {"comment": "onlyif condition is true", "result": False}
+        with patch("salt.state.State._gather_pillar") as state_patch:
+            minion_opts = self.get_temp_config("minion")
+            state_obj = salt.state.State(minion_opts)
+            return_result = state_obj._run_check_onlyif(low_data, {})
+            self.assertEqual(expected_result, return_result)
+
+    def test_verify_unless_list_cmd_invalid(self):
+        low_data = {
+            "state": "cmd",
+            "name": 'echo "something"',
+            "__sls__": "tests.cmd",
+            "__env__": "base",
+            "__id__": "check unless",
+            "unless": ["exit 0", "exit 0"],
+            "order": 10001,
+            "fun": "run",
+        }
+        expected_result = {
+            "comment": "unless condition is true",
+            "result": True,
+            "skip_watch": True,
+        }
+        with patch("salt.state.State._gather_pillar") as state_patch:
+            minion_opts = self.get_temp_config("minion")
+            state_obj = salt.state.State(minion_opts)
+            return_result = state_obj._run_check_unless(low_data, {})
+            self.assertEqual(expected_result, return_result)
+
+    def test_verify_onlyif_list_cmd_invalid(self):
+        low_data = {
+            "state": "cmd",
+            "name": 'echo "something"',
+            "__sls__": "tests.cmd",
+            "__env__": "base",
+            "__id__": "check onlyif",
+            "onlyif": ["exit 1", "exit 1"],
+            "order": 10001,
+            "fun": "run",
+        }
+        expected_result = {
+            "comment": "onlyif condition is false",
+            "result": True,
+            "skip_watch": True,
+        }
+        with patch("salt.state.State._gather_pillar") as state_patch:
+            minion_opts = self.get_temp_config("minion")
+            state_obj = salt.state.State(minion_opts)
+            return_result = state_obj._run_check_onlyif(low_data, {})
+            self.assertEqual(expected_result, return_result)
+
     def test_verify_unless_parse(self):
         low_data = {
             "unless": [{"fun": "test.arg", "args": ["arg1", "arg2"]}],
@@ -376,7 +522,7 @@ class StateCompilerTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
             "__sls__": "tests.cmd",
             "__env__": "base",
             "__id__": "check onlyif",
-            "onlyif": ["/bin/true", "/bin/false"],
+            "onlyif": ["exit 0", "exit 1"],
             "order": 10001,
             "fun": "run",
         }
-- 
2.29.2


openSUSE Build Service is sponsored by