From 2b2028934961b6974df665b42a2b74a21b8edc51 Mon Sep 17 00:00:00 2001 From: nicholasmhughes Date: Tue, 8 Nov 2022 14:14:46 -0500 Subject: [PATCH] fixes saltstack/salt#63042 add ability to ignore symlinks in file.tidied --- changelog/63042.added | 1 + salt/states/file.py | 14 ++++-- tests/pytests/unit/states/file/test_tidied.py | 48 +++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 changelog/63042.added diff --git a/changelog/63042.added b/changelog/63042.added new file mode 100644 index 00000000000..2999b8fcb91 --- /dev/null +++ b/changelog/63042.added @@ -0,0 +1 @@ +Add ability to ignore symlinks in file.tidied diff --git a/salt/states/file.py b/salt/states/file.py index 5cec0deb97f..44cd8919f09 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -1949,10 +1949,11 @@ def tidied( time_comparison="atime", age_size_logical_operator="OR", age_size_only=None, + rmlinks=True, **kwargs ): """ - .. versionchanged:: 3006,3005 + .. versionchanged:: 3006.0,3005 Remove unwanted files based on specific criteria. @@ -2016,7 +2017,7 @@ def tidied( is not old enough will NOT get tidied. A file will need to fulfill BOTH conditions in order to be tidied. Accepts ``OR`` or ``AND``. - .. versionadded:: 3006 + .. versionadded:: 3006.0 age_size_only This parameter can trigger the reduction of age and size conditions @@ -2027,7 +2028,12 @@ def tidied( evaluation down to that specific condition. Path matching and exclusions still apply. - .. versionadded:: 3006 + .. versionadded:: 3006.0 + + rmlinks + Whether or not it's allowed to remove symbolic links + + .. versionadded:: 3006.0 .. code-block:: yaml @@ -2111,6 +2117,8 @@ def tidied( mytimestamp = os.lstat(path).st_mtime else: mytimestamp = os.lstat(path).st_atime + if not rmlinks: + deleteme = False else: # Get timestamp of file or directory if time_comparison == "ctime": diff --git a/tests/pytests/unit/states/file/test_tidied.py b/tests/pytests/unit/states/file/test_tidied.py index 7a34b7125d6..c1873a27320 100644 --- a/tests/pytests/unit/states/file/test_tidied.py +++ b/tests/pytests/unit/states/file/test_tidied.py @@ -1,6 +1,7 @@ import logging import os from datetime import datetime, timedelta +from types import SimpleNamespace import pytest @@ -578,3 +579,50 @@ def test_tidied_filenotfound(tmp_path): "comment": "Nothing to remove from directory {}".format(name), } assert ret == exp + + +def test_tidied_rmlinks(): + name = os.sep + "test" + if salt.utils.platform.is_windows(): + name = "c:" + name + walker = [ + (os.path.join("test", "test1"), [], ["file1"]), + (os.path.join("test", "test2", "test3"), [], []), + (os.path.join("test", "test2"), ["test3"], ["link1"]), + ("test", ["test1", "test2"], ["file3"]), + ] + today_delta = (datetime.today() - timedelta(days=14)) - datetime.utcfromtimestamp(0) + mock_lstat = MagicMock(return_value=SimpleNamespace(st_atime=today_delta)) + remove = MagicMock(name="file.remove") + with patch("os.walk", return_value=walker), patch( + "os.path.islink", side_effect=[False, True, False, False, False, False] + ), patch("os.path.getatime", return_value=today_delta.total_seconds()), patch( + "os.lstat", return_value=mock_lstat + ), patch( + "os.path.getsize", return_value=10 + ), patch.dict( + filestate.__opts__, {"test": False} + ), patch.dict( + filestate.__salt__, {"file.remove": remove} + ), patch( + "os.path.isdir", return_value=True + ): + ret = filestate.tidied( + name=name, + age=1, + size=9, + rmlinks=False, + ) + exp = { + "name": name, + "changes": { + "removed": [ + os.path.join("test", "test1", "file1"), + os.path.join("test", "file3"), + ] + }, + "result": True, + "comment": "Removed 2 files or directories from directory {}".format(name), + } + assert ret == exp + assert remove.call_count == 2