File fix-state.apply-in-test-mode-with-file-state-module-.patch of Package salt

From ed567e5f339f7bf95d4361ac47e67427db71714c Mon Sep 17 00:00:00 2001
From: Victor Zhestkov <Victor.Zhestkov@suse.com>
Date: Thu, 1 Sep 2022 14:44:26 +0300
Subject: [PATCH] Fix state.apply in test mode with file state module
 on user/group checking (bsc#1202167)

* Do not fail on checking user/group in test mode

* fixes saltstack/salt#61846 reporting of errors in test mode

Co-authored-by: nicholasmhughes <nicholasmhughes@gmail.com>

* Add tests for _check_user usage

Co-authored-by: nicholasmhughes <nicholasmhughes@gmail.com>
---
 changelog/61846.fixed                         |  1 +
 salt/states/file.py                           |  5 ++
 tests/pytests/unit/states/file/test_copy.py   | 35 ++++++++++++
 .../unit/states/file/test_directory.py        | 55 +++++++++++++++++++
 .../unit/states/file/test_filestate.py        | 42 ++++++++++++++
 .../pytests/unit/states/file/test_managed.py  | 31 +++++++++++
 6 files changed, 169 insertions(+)
 create mode 100644 changelog/61846.fixed

diff --git a/changelog/61846.fixed b/changelog/61846.fixed
new file mode 100644
index 0000000000..c4024efe9f
--- /dev/null
+++ b/changelog/61846.fixed
@@ -0,0 +1 @@
+Fix the reporting of errors for file.directory in test mode
diff --git a/salt/states/file.py b/salt/states/file.py
index a6288025e5..39cf83b78e 100644
--- a/salt/states/file.py
+++ b/salt/states/file.py
@@ -379,6 +379,11 @@ def _check_user(user, group):
         gid = __salt__["file.group_to_gid"](group)
         if gid == "":
             err += "Group {} is not available".format(group)
+    if err and __opts__["test"]:
+        # Write the warning with error message, but prevent failing,
+        # in case of applying the state in test mode.
+        log.warning(err)
+        return ""
     return err
 
 
diff --git a/tests/pytests/unit/states/file/test_copy.py b/tests/pytests/unit/states/file/test_copy.py
index ce7161f02d..a11adf5ae0 100644
--- a/tests/pytests/unit/states/file/test_copy.py
+++ b/tests/pytests/unit/states/file/test_copy.py
@@ -205,3 +205,38 @@ def test_copy(tmp_path):
                     )
                     res = filestate.copy_(name, source, group=group, preserve=False)
                     assert res == ret
+
+
+def test_copy_test_mode_user_group_not_present():
+    """
+    Test file copy in test mode with no user or group existing
+    """
+    source = "/tmp/src_copy_no_user_group_test_mode"
+    filename = "/tmp/copy_no_user_group_test_mode"
+    with patch.dict(
+        filestate.__salt__,
+        {
+            "file.group_to_gid": MagicMock(side_effect=["1234", "", ""]),
+            "file.user_to_uid": MagicMock(side_effect=["", "4321", ""]),
+            "file.get_mode": MagicMock(return_value="0644"),
+        },
+    ), patch.dict(filestate.__opts__, {"test": True}), patch.object(
+        os.path, "exists", return_value=True
+    ):
+        ret = filestate.copy_(
+            source, filename, group="nonexistinggroup", user="nonexistinguser"
+        )
+        assert ret["result"] is not False
+        assert "is not available" not in ret["comment"]
+
+        ret = filestate.copy_(
+            source, filename, group="nonexistinggroup", user="nonexistinguser"
+        )
+        assert ret["result"] is not False
+        assert "is not available" not in ret["comment"]
+
+        ret = filestate.copy_(
+            source, filename, group="nonexistinggroup", user="nonexistinguser"
+        )
+        assert ret["result"] is not False
+        assert "is not available" not in ret["comment"]
diff --git a/tests/pytests/unit/states/file/test_directory.py b/tests/pytests/unit/states/file/test_directory.py
index 0e15e1d3ca..1287609c6a 100644
--- a/tests/pytests/unit/states/file/test_directory.py
+++ b/tests/pytests/unit/states/file/test_directory.py
@@ -291,3 +291,58 @@ def test_directory():
                             assert (
                                 filestate.directory(name, user=user, group=group) == ret
                             )
+
+
+def test_directory_test_mode_user_group_not_present():
+    name = "/etc/testdir"
+    user = "salt"
+    group = "saltstack"
+    if salt.utils.platform.is_windows():
+        name = name.replace("/", "\\")
+
+    ret = {
+        "name": name,
+        "result": None,
+        "comment": "",
+        "changes": {name: {"directory": "new"}},
+    }
+
+    if salt.utils.platform.is_windows():
+        comt = 'The directory "{}" will be changed' "".format(name)
+    else:
+        comt = "The following files will be changed:\n{}:" " directory - new\n".format(
+            name
+        )
+    ret["comment"] = comt
+
+    mock_f = MagicMock(return_value=False)
+    mock_uid = MagicMock(
+        side_effect=[
+            "",
+            "U12",
+            "",
+        ]
+    )
+    mock_gid = MagicMock(
+        side_effect=[
+            "G12",
+            "",
+            "",
+        ]
+    )
+    mock_error = CommandExecutionError
+    with patch.dict(
+        filestate.__salt__,
+        {
+            "file.user_to_uid": mock_uid,
+            "file.group_to_gid": mock_gid,
+            "file.stats": mock_f,
+        },
+    ), patch("salt.utils.win_dacl.get_sid", mock_error), patch.object(
+        os.path, "isdir", mock_f
+    ), patch.dict(
+        filestate.__opts__, {"test": True}
+    ):
+        assert filestate.directory(name, user=user, group=group) == ret
+        assert filestate.directory(name, user=user, group=group) == ret
+        assert filestate.directory(name, user=user, group=group) == ret
diff --git a/tests/pytests/unit/states/file/test_filestate.py b/tests/pytests/unit/states/file/test_filestate.py
index 2f9f369fb2..c373cb3449 100644
--- a/tests/pytests/unit/states/file/test_filestate.py
+++ b/tests/pytests/unit/states/file/test_filestate.py
@@ -577,3 +577,45 @@ def test_mod_run_check_cmd():
         assert filestate.mod_run_check_cmd(cmd, filename) == ret
 
         assert filestate.mod_run_check_cmd(cmd, filename)
+
+
+def test_recurse_test_mode_user_group_not_present():
+    """
+    Test file recurse in test mode with no user or group existing
+    """
+    filename = "/tmp/recurse_no_user_group_test_mode"
+    source = "salt://tmp/src_recurse_no_user_group_test_mode"
+    mock_l = MagicMock(return_value=[])
+    mock_emt = MagicMock(return_value=["tmp/src_recurse_no_user_group_test_mode"])
+    with patch.dict(
+        filestate.__salt__,
+        {
+            "file.group_to_gid": MagicMock(side_effect=["1234", "", ""]),
+            "file.user_to_uid": MagicMock(side_effect=["", "4321", ""]),
+            "file.get_mode": MagicMock(return_value="0644"),
+            "file.source_list": MagicMock(return_value=[source, ""]),
+            "cp.list_master_dirs": mock_emt,
+            "cp.list_master": mock_l,
+        },
+    ), patch.dict(filestate.__opts__, {"test": True}), patch.object(
+        os.path, "exists", return_value=True
+    ), patch.object(
+        os.path, "isdir", return_value=True
+    ):
+        ret = filestate.recurse(
+            filename, source, group="nonexistinggroup", user="nonexistinguser"
+        )
+        assert ret["result"] is not False
+        assert "is not available" not in ret["comment"]
+
+        ret = filestate.recurse(
+            filename, source, group="nonexistinggroup", user="nonexistinguser"
+        )
+        assert ret["result"] is not False
+        assert "is not available" not in ret["comment"]
+
+        ret = filestate.recurse(
+            filename, source, group="nonexistinggroup", user="nonexistinguser"
+        )
+        assert ret["result"] is not False
+        assert "is not available" not in ret["comment"]
diff --git a/tests/pytests/unit/states/file/test_managed.py b/tests/pytests/unit/states/file/test_managed.py
index 9d9fb17717..0b341e09a9 100644
--- a/tests/pytests/unit/states/file/test_managed.py
+++ b/tests/pytests/unit/states/file/test_managed.py
@@ -373,3 +373,34 @@ def test_managed():
                                         filestate.managed(name, user=user, group=group)
                                         == ret
                                     )
+
+
+def test_managed_test_mode_user_group_not_present():
+    """
+    Test file managed in test mode with no user or group existing
+    """
+    filename = "/tmp/managed_no_user_group_test_mode"
+    with patch.dict(
+        filestate.__salt__,
+        {
+            "file.group_to_gid": MagicMock(side_effect=["1234", "", ""]),
+            "file.user_to_uid": MagicMock(side_effect=["", "4321", ""]),
+        },
+    ), patch.dict(filestate.__opts__, {"test": True}):
+        ret = filestate.managed(
+            filename, group="nonexistinggroup", user="nonexistinguser"
+        )
+        assert ret["result"] is not False
+        assert "is not available" not in ret["comment"]
+
+        ret = filestate.managed(
+            filename, group="nonexistinggroup", user="nonexistinguser"
+        )
+        assert ret["result"] is not False
+        assert "is not available" not in ret["comment"]
+
+        ret = filestate.managed(
+            filename, group="nonexistinggroup", user="nonexistinguser"
+        )
+        assert ret["result"] is not False
+        assert "is not available" not in ret["comment"]
-- 
2.37.2


openSUSE Build Service is sponsored by