From a689a32f00e29c74b24bb52db6e555931b9ccd36 Mon Sep 17 00:00:00 2001
From: Petr Pavlu <31453820+petrpavlu@users.noreply.github.com>
Date: Fri, 8 Jul 2022 10:11:58 +0200
Subject: [PATCH] Fix salt.states.file.managed() for
 follow_symlinks=True and test=True (bsc#1199372) (#536)

When managing file /etc/test as follows:
> file /etc/test:
>   file.managed:
>     - name: /etc/test
>     - source: salt://config/test
>     - mode: 644
>     - follow_symlinks: True

and with /etc/test being a symlink to a different file, an invocation of
"salt-call '*' state.apply test=True" can report that the file should be
updated even when a subsequent run of the same command without the test
parameter makes no changes.

The problem is that the test code path doesn't take correctly into
account the follow_symlinks=True setting and ends up comparing
permissions of the symlink instead of its target file.

The patch addresses the problem by extending functions
salt.modules.file.check_managed(), check_managed_changes() and
check_file_meta() to have the follow_symlinks parameter which gets
propagated to the salt.modules.file.stats() call and by updating
salt.states.file.managed() to forward the same parameter to
salt.modules.file.check_managed_changes().

Fixes #62066.

[Cherry-picked from upstream commit
95bfbe31a2dc54723af3f1783d40de152760fe1a. The backport drops
tests/pytests/unit/modules/file/test_file_check.py because the test
requires unavailable patching of loader modules.]
---
 changelog/62066.fixed |  1 +
 salt/modules/file.py  | 31 +++++++++++++++++++++++++++----
 salt/states/file.py   |  1 +
 3 files changed, 29 insertions(+), 4 deletions(-)
 create mode 100644 changelog/62066.fixed

diff --git a/changelog/62066.fixed b/changelog/62066.fixed
new file mode 100644
index 0000000000..68216a03c1
--- /dev/null
+++ b/changelog/62066.fixed
@@ -0,0 +1 @@
+Fixed salt.states.file.managed() for follow_symlinks=True and test=True
diff --git a/salt/modules/file.py b/salt/modules/file.py
index 8a9fd84a99..ece572a82d 100644
--- a/salt/modules/file.py
+++ b/salt/modules/file.py
@@ -4748,10 +4748,17 @@ def check_managed(
         saltenv,
         contents=None,
         skip_verify=False,
+        follow_symlinks=False,
         **kwargs):
     '''
     Check to see what changes need to be made for a file
 
+    follow_symlinks
+        If the desired path is a symlink, follow it and check the permissions
+        of the file to which the symlink points.
+
+        .. versionadded:: 3005
+
     CLI Example:
 
     .. code-block:: bash
@@ -4787,7 +4794,8 @@ def check_managed(
             __clean_tmp(sfn)
             return False, comments
     changes = check_file_meta(name, sfn, source, source_sum, user,
-                              group, mode, attrs, saltenv, contents)
+                              group, mode, attrs, saltenv, contents,
+                              follow_symlinks=follow_symlinks)
     # Ignore permission for files written temporary directories
     # Files in any path will still be set correctly using get_managed()
     if name.startswith(tempfile.gettempdir()):
@@ -4819,10 +4827,17 @@ def check_managed_changes(
         contents=None,
         skip_verify=False,
         keep_mode=False,
+        follow_symlinks=False,
         **kwargs):
     '''
     Return a dictionary of what changes need to be made for a file
 
+    follow_symlinks
+        If the desired path is a symlink, follow it and check the permissions
+        of the file to which the symlink points.
+
+        .. versionadded:: 3005
+
     CLI Example:
 
     .. code-block:: bash
@@ -4870,7 +4885,8 @@ def check_managed_changes(
                 except Exception as exc:  # pylint: disable=broad-except
                     log.warning('Unable to stat %s: %s', sfn, exc)
     changes = check_file_meta(name, sfn, source, source_sum, user,
-                              group, mode, attrs, saltenv, contents)
+                              group, mode, attrs, saltenv, contents,
+                              follow_symlinks=follow_symlinks)
     __clean_tmp(sfn)
     return changes
 
@@ -4885,7 +4901,8 @@ def check_file_meta(
         mode,
         attrs,
         saltenv,
-        contents=None):
+        contents=None,
+        follow_symlinks=False):
     '''
     Check for the changes in the file metadata.
 
@@ -4935,6 +4952,12 @@ def check_file_meta(
 
     contents
         File contents
+
+    follow_symlinks
+        If the desired path is a symlink, follow it and check the permissions
+        of the file to which the symlink points.
+
+        .. versionadded:: 3005
     '''
     changes = {}
     if not source_sum:
@@ -4942,7 +4965,7 @@ def check_file_meta(
 
     try:
         lstats = stats(name, hash_type=source_sum.get('hash_type', None),
-                       follow_symlinks=False)
+                       follow_symlinks=follow_symlinks)
     except CommandExecutionError:
         lstats = {}
 
diff --git a/salt/states/file.py b/salt/states/file.py
index 847cbace51..2e0a1c5835 100644
--- a/salt/states/file.py
+++ b/salt/states/file.py
@@ -3043,6 +3043,7 @@ def managed(name,
                     contents,
                     skip_verify,
                     keep_mode,
+                    follow_symlinks=follow_symlinks,
                     **kwargs
                 )
 
-- 
2.36.1


