From 554da0c21efa1e891bc21ed25bd9e142782c35f0 Mon Sep 17 00:00:00 2001 From: twangboy Date: Thu, 12 Sep 2024 09:49:19 -0600 Subject: [PATCH] Compare full paths --- salt/states/file.py | 21 +++++++--- .../pytests/unit/states/file/test_recurse.py | 42 +++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 tests/pytests/unit/states/file/test_recurse.py diff --git a/salt/states/file.py b/salt/states/file.py index 717dafb4720..e4d9b7ece98 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -282,6 +282,7 @@ import difflib import itertools import logging import os +import pathlib import posixpath import re import shutil @@ -557,15 +558,23 @@ def _gen_recurse_managed_files( managed_directories.add(mdest) keep.add(mdest) - # Sets are randomly ordered. We need to make sure symlinks are always at the - # end, so we need to use a list + # Sets are randomly ordered. We need to use a list so we can make sure + # symlinks are always at the end. This is necessary because the file must + # exist before we can create a symlink to it. See issue: + # https://github.com/saltstack/salt/issues/64630 new_managed_files = list(managed_files) # Now let's move all the symlinks to the end - for symlink in managed_symlinks: - for file in managed_files: - if file[0].endswith(os.sep + symlink[0]): + for link_src_relpath, _ in managed_symlinks: + for file_dest, file_src in managed_files: + # We need to convert relpath to fullpath. We're using pathlib to + # be platform-agnostic + symlink_full_path = pathlib.Path(f"{name}\\{link_src_relpath}") + file_dest_full_path = pathlib.Path(file_dest) + if symlink_full_path == file_dest_full_path: new_managed_files.append( - new_managed_files.pop(new_managed_files.index(file)) + new_managed_files.pop( + new_managed_files.index((file_dest, file_src)) + ) ) return new_managed_files, managed_directories, managed_symlinks, keep diff --git a/tests/pytests/unit/states/file/test_recurse.py b/tests/pytests/unit/states/file/test_recurse.py new file mode 100644 index 00000000000..dbbf1e1d2fe --- /dev/null +++ b/tests/pytests/unit/states/file/test_recurse.py @@ -0,0 +1,42 @@ +import logging +import pathlib + +import pytest + +import salt.states.file as filestate +from tests.support.mock import MagicMock, patch + +log = logging.getLogger(__name__) + + +@pytest.fixture +def configure_loader_modules(): + return {filestate: {"__salt__": {}, "__opts__": {}, "__env__": "base"}} + + +def test__gen_recurse_managed_files(): + """ + Test _gen_recurse_managed_files to make sure it puts symlinks at the end of the list of files. + """ + target_dir = pathlib.Path("\\some\\path\\target") + cp_list_master = MagicMock( + return_value=[ + "target/symlink", + "target/just_a_file.txt", + "target/not_a_symlink/symlink", + "target/notasymlink", + ], + ) + cp_list_master_symlinks = MagicMock( + return_value={"target/symlink": f"{target_dir}\\not_a_symlink\\symlink"} + ) + patch_salt = { + "cp.list_master": cp_list_master, + "cp.list_master_symlinks": cp_list_master_symlinks, + } + with patch.dict(filestate.__salt__, patch_salt): + files, dirs, links, keep = filestate._gen_recurse_managed_files( + name=str(target_dir), source=f"salt://{target_dir.name}", keep_symlinks=True + ) + expected = ("\\some\\path\\target\\symlink", "salt://target/symlink?saltenv=base") + assert files[-1] == expected