diff --git a/changelog/64477.fixed.md b/changelog/64477.fixed.md new file mode 100644 index 00000000000..d43f01714d9 --- /dev/null +++ b/changelog/64477.fixed.md @@ -0,0 +1 @@ +Fix file.symlink will not replace/update existing symlink diff --git a/salt/states/file.py b/salt/states/file.py index 415c3688e1e..ed9b65be1d6 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -1791,9 +1791,11 @@ def symlink( if __salt__["file.is_link"](name): # The link exists, verify that it matches the target - if os.path.normpath(__salt__["file.readlink"](name)) == os.path.normpath( + if os.path.normpath(__salt__["file.readlink"](name)) != os.path.normpath( target ): + __salt__["file.remove"](name) + else: if _check_symlink_ownership(name, user, group, win_owner): # The link looks good! if salt.utils.platform.is_windows(): diff --git a/tests/pytests/functional/states/test_file.py b/tests/pytests/functional/states/test_file.py index cea0552f365..a28ab64a35f 100644 --- a/tests/pytests/functional/states/test_file.py +++ b/tests/pytests/functional/states/test_file.py @@ -201,3 +201,25 @@ def test_file_managed_web_source_etag_operation( # The modified time of the cached file now changes assert cached_file_mtime != os.path.getmtime(cached_file) + + +def test_file_symlink_replace_existing_link(states, tmp_path): + # symlink name and target for state + name = tmp_path / "foo" + target = tmp_path / "baz" + + # create existing symlink to replace + old_target = tmp_path / "bar" + name.symlink_to(old_target) + + ret = states.file.symlink( + name=str(name), + target=str(target), + ) + + assert ret.filtered == { + "name": str(name), + "changes": {"new": str(name)}, + "comment": f"Created new symlink {str(name)} -> {str(target)}", + "result": True, + }