From 0325170e94b8fb5f0ac174c449939fdbebe652dc Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Wed, 25 Jun 2025 17:49:49 -0700 Subject: [PATCH 1/2] copy: fix appending symlink information Signed-off-by: Abhijeet Kasurde --- changelogs/fragments/copy_fix.yml | 3 +++ lib/ansible/plugins/action/copy.py | 2 +- test/units/plugins/action/test_copy.py | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/copy_fix.yml create mode 100644 test/units/plugins/action/test_copy.py diff --git a/changelogs/fragments/copy_fix.yml b/changelogs/fragments/copy_fix.yml new file mode 100644 index 00000000000..0ee7c1843e4 --- /dev/null +++ b/changelogs/fragments/copy_fix.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - copy - fix appending symlinks information. diff --git a/lib/ansible/plugins/action/copy.py b/lib/ansible/plugins/action/copy.py index 89a6a8f1f95..e2fd9e9089b 100644 --- a/lib/ansible/plugins/action/copy.py +++ b/lib/ansible/plugins/action/copy.py @@ -187,7 +187,7 @@ def _walk_dirs(topdir, base_path=None, local_follow=False, trailing_slash_detect offset += 1 if os.path.islink(topdir) and not local_follow: - r_files['symlinks'] = (os.readlink(topdir), os.path.basename(topdir)) + r_files['symlinks'].append((os.readlink(topdir), os.path.basename(topdir))) return r_files dir_stats = os.stat(topdir) diff --git a/test/units/plugins/action/test_copy.py b/test/units/plugins/action/test_copy.py new file mode 100644 index 00000000000..e5057d34ffd --- /dev/null +++ b/test/units/plugins/action/test_copy.py @@ -0,0 +1,21 @@ +# Copyright: Contributors to the Ansible project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from ansible.plugins.action import copy + + +def test_walk_dirs_with_symlink(mocker): + mocker.patch("os.path.islink", return_value=True) + mocker.patch("os.readlink", return_value="/path/to/real/file.txt") + mocker.patch("os.path.basename", return_value="file.txt") + + ret = copy._walk_dirs("/path/to/symlink", local_follow=False) + expected = { + "directories": [], + "files": [], + "symlinks": [ + ("/path/to/real/file.txt", "file.txt"), + ], + } + assert ret == expected From 03958e73270e6d6df4b9f3126e0e2b4553bcc018 Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Thu, 26 Jun 2025 09:47:11 -0700 Subject: [PATCH 2/2] Added integration test Signed-off-by: Abhijeet Kasurde --- test/integration/targets/copy/tasks/tests.yml | 43 +++++++++++++++++++ test/units/plugins/action/test_copy.py | 21 --------- 2 files changed, 43 insertions(+), 21 deletions(-) delete mode 100644 test/units/plugins/action/test_copy.py diff --git a/test/integration/targets/copy/tasks/tests.yml b/test/integration/targets/copy/tasks/tests.yml index 2b6b5c6ff60..f380bae6517 100644 --- a/test/integration/targets/copy/tasks/tests.yml +++ b/test/integration/targets/copy/tasks/tests.yml @@ -2531,3 +2531,46 @@ state: absent loop: - '{{ remote_file }}' + +- name: Test copy with local_follow=False and dest is a symlink + block: + - name: execute - Create a test dest dir + file: + path: '{{ item }}' + state: directory + loop: + - '{{ remote_tmp_dir }}/testcase_local_follow_false_symlink_dest' + - '{{ remote_tmp_dir }}/dest' + + - name: Create a dest symlink + file: + src: "{{ remote_tmp_dir }}/dest" + state: link + dest: "{{ remote_tmp_dir }}/sym_dest" + + - name: Copy a file to the dest symlink + copy: + src: "{{ remote_tmp_dir }}/sym_dest" + dest: "{{ remote_tmp_dir }}/testcase_local_follow_false_symlink_dest" + local_follow: False + remote_src: True + register: copy_result + + - name: Gather info about copied symlink + stat: + path: "{{ remote_tmp_dir }}/testcase_local_follow_false_symlink_dest/sym_dest" + register: stat_result + + - name: Assert that the file has been copied + assert: + that: + - copy_result is changed + - stat_result.stat.exists + always: + - name: execute - Clean up + file: + path: '{{ item }}' + state: absent + loop: + - '{{ remote_tmp_dir }}/testcase_local_follow_false_symlink_dest' + - '{{ remote_tmp_dir }}/dest' diff --git a/test/units/plugins/action/test_copy.py b/test/units/plugins/action/test_copy.py deleted file mode 100644 index e5057d34ffd..00000000000 --- a/test/units/plugins/action/test_copy.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright: Contributors to the Ansible project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - - -from ansible.plugins.action import copy - - -def test_walk_dirs_with_symlink(mocker): - mocker.patch("os.path.islink", return_value=True) - mocker.patch("os.readlink", return_value="/path/to/real/file.txt") - mocker.patch("os.path.basename", return_value="file.txt") - - ret = copy._walk_dirs("/path/to/symlink", local_follow=False) - expected = { - "directories": [], - "files": [], - "symlinks": [ - ("/path/to/real/file.txt", "file.txt"), - ], - } - assert ret == expected