diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
commit | a453ac31f3428614cceb99027f8efbdb9258a40b (patch) | |
tree | f61f87408f32a8511cbd91799f9cececb53e0374 /test/integration/targets/copy | |
parent | Initial commit. (diff) | |
download | ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.tar.xz ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.zip |
Adding upstream version 2.10.7+merged+base+2.10.8+dfsg.upstream/2.10.7+merged+base+2.10.8+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test/integration/targets/copy')
18 files changed, 2798 insertions, 0 deletions
diff --git a/test/integration/targets/copy/aliases b/test/integration/targets/copy/aliases new file mode 100644 index 00000000..db9bbd8c --- /dev/null +++ b/test/integration/targets/copy/aliases @@ -0,0 +1,4 @@ +needs/root +shippable/posix/group2 +destructive +skip/aix diff --git a/test/integration/targets/copy/defaults/main.yml b/test/integration/targets/copy/defaults/main.yml new file mode 100644 index 00000000..8e9a5836 --- /dev/null +++ b/test/integration/targets/copy/defaults/main.yml @@ -0,0 +1,2 @@ +--- +remote_unprivileged_user: tmp_ansible_test_user diff --git a/test/integration/targets/copy/files/foo.txt b/test/integration/targets/copy/files/foo.txt new file mode 100644 index 00000000..7c6ded14 --- /dev/null +++ b/test/integration/targets/copy/files/foo.txt @@ -0,0 +1 @@ +foo.txt diff --git a/test/integration/targets/copy/files/subdir/bar.txt b/test/integration/targets/copy/files/subdir/bar.txt new file mode 100644 index 00000000..76018072 --- /dev/null +++ b/test/integration/targets/copy/files/subdir/bar.txt @@ -0,0 +1 @@ +baz diff --git a/test/integration/targets/copy/files/subdir/subdir1/bar.txt b/test/integration/targets/copy/files/subdir/subdir1/bar.txt new file mode 120000 index 00000000..315e865d --- /dev/null +++ b/test/integration/targets/copy/files/subdir/subdir1/bar.txt @@ -0,0 +1 @@ +../bar.txt
\ No newline at end of file diff --git a/test/integration/targets/copy/files/subdir/subdir2/baz.txt b/test/integration/targets/copy/files/subdir/subdir2/baz.txt new file mode 100644 index 00000000..76018072 --- /dev/null +++ b/test/integration/targets/copy/files/subdir/subdir2/baz.txt @@ -0,0 +1 @@ +baz diff --git a/test/integration/targets/copy/files/subdir/subdir2/subdir3/subdir4/qux.txt b/test/integration/targets/copy/files/subdir/subdir2/subdir3/subdir4/qux.txt new file mode 100644 index 00000000..78df5b06 --- /dev/null +++ b/test/integration/targets/copy/files/subdir/subdir2/subdir3/subdir4/qux.txt @@ -0,0 +1 @@ +qux
\ No newline at end of file diff --git a/test/integration/targets/copy/meta/main.yml b/test/integration/targets/copy/meta/main.yml new file mode 100644 index 00000000..06d4fd29 --- /dev/null +++ b/test/integration/targets/copy/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - prepare_tests + - setup_nobody diff --git a/test/integration/targets/copy/tasks/acls.yml b/test/integration/targets/copy/tasks/acls.yml new file mode 100644 index 00000000..9a3be9b0 --- /dev/null +++ b/test/integration/targets/copy/tasks/acls.yml @@ -0,0 +1,33 @@ +- block: + - block: + - name: Testing ACLs + copy: + content: "TEST" + mode: 0644 + dest: "~/test.txt" + + - shell: getfacl ~/test.txt + register: acls + + become: yes + become_user: "{{ remote_unprivileged_user }}" + + - name: Check that there are no ACLs leftovers + assert: + that: + - "'user:{{ remote_unprivileged_user }}:r-x\t#effective:r--' not in acls.stdout_lines" + + - name: Check that permissions match with what was set in the mode param + assert: + that: + - "'user::rw-' in acls.stdout_lines" + - "'group::r--' in acls.stdout_lines" + - "'other::r--' in acls.stdout_lines" + + always: + - name: Clean up + file: + path: "~/test.txt" + state: absent + become: yes + become_user: "{{ remote_unprivileged_user }}" diff --git a/test/integration/targets/copy/tasks/check_mode.yml b/test/integration/targets/copy/tasks/check_mode.yml new file mode 100644 index 00000000..5b405cc4 --- /dev/null +++ b/test/integration/targets/copy/tasks/check_mode.yml @@ -0,0 +1,126 @@ +- block: + + - name: check_mode - Create another clean copy of 'subdir' not messed with by previous tests (check_mode) + copy: + src: subdir + dest: 'checkmode_subdir/' + directory_mode: 0700 + local_follow: False + check_mode: true + register: check_mode_subdir_first + + - name: check_mode - Stat the new dir to make sure it really doesn't exist + stat: + path: 'checkmode_subdir/' + register: check_mode_subdir_first_stat + + - name: check_mode - Actually do it + copy: + src: subdir + dest: 'checkmode_subdir/' + directory_mode: 0700 + local_follow: False + register: check_mode_subdir_real + + - name: check_mode - Stat the new dir to make sure it really exists + stat: + path: 'checkmode_subdir/' + register: check_mode_subdir_real_stat + + # Quick sanity before we move on + - assert: + that: + - check_mode_subdir_first is changed + - not check_mode_subdir_first_stat.stat.exists + - check_mode_subdir_real is changed + - check_mode_subdir_real_stat.stat.exists + + # Do some finagling here. First, use check_mode to ensure it never gets + # created. Then actualy create it, and use check_mode to ensure that doing + # the same copy gets marked as no change. + # + # This same pattern repeats for several other src/dest combinations. + - name: check_mode - Ensure dest with trailing / never gets created but would be without check_mode + copy: + remote_src: true + src: 'checkmode_subdir/' + dest: 'destdir_should_never_exist_because_of_check_mode/' + follow: true + check_mode: true + register: check_mode_trailing_slash_first + + - name: check_mode - Stat the new dir to make sure it really doesn't exist + stat: + path: 'destdir_should_never_exist_because_of_check_mode/' + register: check_mode_trailing_slash_first_stat + + - name: check_mode - Create the above copy for real now (without check_mode) + copy: + remote_src: true + src: 'checkmode_subdir/' + dest: 'destdir_should_never_exist_because_of_check_mode/' + register: check_mode_trailing_slash_real + + - name: check_mode - Stat the new dir to make sure it really exists + stat: + path: 'destdir_should_never_exist_because_of_check_mode/' + register: check_mode_trailing_slash_real_stat + + - name: check_mode - Do the same copy yet again (with check_mode this time) to ensure it's marked unchanged + copy: + remote_src: true + src: 'checkmode_subdir/' + dest: 'destdir_should_never_exist_because_of_check_mode/' + check_mode: true + register: check_mode_trailing_slash_second + + # Repeat the same basic pattern here. + + - name: check_mode - Do another basic copy (with check_mode) + copy: + src: foo.txt + dest: "{{ remote_dir }}/foo-check_mode.txt" + mode: 0444 + check_mode: true + register: check_mode_foo_first + + - name: check_mode - Stat the new file to make sure it really doesn't exist + stat: + path: "{{ remote_dir }}/foo-check_mode.txt" + register: check_mode_foo_first_stat + + - name: check_mode - Do the same basic copy (without check_mode) + copy: + src: foo.txt + dest: "{{ remote_dir }}/foo-check_mode.txt" + mode: 0444 + register: check_mode_foo_real + + - name: check_mode - Stat the new file to make sure it really exists + stat: + path: "{{ remote_dir }}/foo-check_mode.txt" + register: check_mode_foo_real_stat + + - name: check_mode - And again (with check_mode) + copy: + src: foo.txt + dest: "{{ remote_dir }}/foo-check_mode.txt" + mode: 0444 + register: check_mode_foo_second + + - assert: + that: + - check_mode_subdir_first is changed + + - check_mode_trailing_slash_first is changed + # TODO: This is a legitimate bug + #- not check_mode_trailing_slash_first_stat.stat.exists + - check_mode_trailing_slash_real is changed + - check_mode_trailing_slash_real_stat.stat.exists + - check_mode_trailing_slash_second is not changed + + - check_mode_foo_first is changed + - not check_mode_foo_first_stat.stat.exists + - check_mode_foo_real is changed + - check_mode_foo_real_stat.stat.exists + - check_mode_foo_second is not changed diff --git a/test/integration/targets/copy/tasks/dest_in_non_existent_directories.yml b/test/integration/targets/copy/tasks/dest_in_non_existent_directories.yml new file mode 100644 index 00000000..c86caa1e --- /dev/null +++ b/test/integration/targets/copy/tasks/dest_in_non_existent_directories.yml @@ -0,0 +1,29 @@ +# src is a file, dest is a non-existent directory (2 levels of directories): +# checks that dest is created +- name: Ensure that dest top directory doesn't exist + file: + path: '{{ remote_dir }}/{{ item.dest.split("/")[0] }}' + state: absent + +- name: Copy file, dest is a nonexistent target directory + copy: + src: '{{ item.src }}' + dest: '{{ remote_dir }}/{{ item.dest }}' + register: copy_result + +- name: assert copy worked + assert: + that: + - 'copy_result is successful' + - 'copy_result is changed' + +- name: stat copied file + stat: + path: '{{ remote_dir }}/{{ item.check }}' + register: stat_file_in_dir_result + +- name: assert that file exists + assert: + that: + - stat_file_in_dir_result.stat.exists + - stat_file_in_dir_result.stat.isreg diff --git a/test/integration/targets/copy/tasks/dest_in_non_existent_directories_remote_src.yml b/test/integration/targets/copy/tasks/dest_in_non_existent_directories_remote_src.yml new file mode 100644 index 00000000..fad53e71 --- /dev/null +++ b/test/integration/targets/copy/tasks/dest_in_non_existent_directories_remote_src.yml @@ -0,0 +1,43 @@ +# src is a file, dest is a non-existent directory (2 levels of directories): +# checks that dest is created +- name: Ensure that dest top directory doesn't exist + file: + path: '{{ remote_dir }}/{{ item.dest.split("/")[0] }}' + state: absent + +- name: create subdir + file: + path: subdir + state: directory + +- name: create src file + file: + path: "{{ item }}" + state: touch + loop: + - foo.txt + - subdir/bar.txt + +- name: Copy file, dest is a nonexistent target directory + copy: + src: '{{ item.src }}' + dest: '{{ remote_dir }}/{{ item.dest }}' + remote_src: true + register: copy_result + +- name: assert copy worked + assert: + that: + - 'copy_result is successful' + - 'copy_result is changed' + +- name: stat copied file + stat: + path: '{{ remote_dir }}/{{ item.check }}' + register: stat_file_in_dir_result + +- name: assert that file exists + assert: + that: + - stat_file_in_dir_result.stat.exists + - stat_file_in_dir_result.stat.isreg diff --git a/test/integration/targets/copy/tasks/main.yml b/test/integration/targets/copy/tasks/main.yml new file mode 100644 index 00000000..33a92bf9 --- /dev/null +++ b/test/integration/targets/copy/tasks/main.yml @@ -0,0 +1,117 @@ +- block: + + - name: Create a local temporary directory + shell: mktemp -d /tmp/ansible_test.XXXXXXXXX + register: tempfile_result + delegate_to: localhost + + - set_fact: + local_temp_dir: '{{ tempfile_result.stdout }}' + remote_dir: '{{ output_dir }}' + symlinks: + ansible-test-abs-link: /tmp/ansible-test-abs-link + ansible-test-abs-link-dir: /tmp/ansible-test-abs-link-dir + circles: ../ + invalid: invalid + invalid2: ../invalid + out_of_tree_circle: /tmp/ansible-test-link-dir/out_of_tree_circle + subdir3: ../subdir2/subdir3 + + - file: path={{local_temp_dir}} state=directory + name: ensure temp dir exists + + # file cannot do this properly, use command instead + - name: Create symbolic link + command: "ln -s '{{ item.value }}' '{{ item.key }}'" + args: + chdir: '{{role_path}}/files/subdir/subdir1' + warn: no + with_dict: "{{ symlinks }}" + + - name: Create remote unprivileged remote user + user: + name: '{{ remote_unprivileged_user }}' + register: user + + - name: Check sudoers dir + stat: + path: /etc/sudoers.d + register: etc_sudoers + + - name: Set sudoers.d path fact + set_fact: + sudoers_d_file: "{{ '/etc/sudoers.d' if etc_sudoers.stat.exists else '/usr/local/etc/sudoers.d' }}/{{ remote_unprivileged_user }}" + + - name: Create sudoers file + copy: + dest: "{{ sudoers_d_file }}" + content: "{{ remote_unprivileged_user }} ALL=(ALL) NOPASSWD: ALL" + + - file: + path: "{{ user.home }}/.ssh" + owner: '{{ remote_unprivileged_user }}' + state: directory + mode: 0700 + + - name: Duplicate authorized_keys + copy: + src: $HOME/.ssh/authorized_keys + dest: '{{ user.home }}/.ssh/authorized_keys' + owner: '{{ remote_unprivileged_user }}' + mode: 0600 + remote_src: yes + + - file: + path: "{{ remote_dir }}" + state: directory + remote_user: '{{ remote_unprivileged_user }}' + + # execute tests tasks using an unprivileged user, this is useful to avoid + # local/remote ambiguity when controller and managed hosts are identical. + - import_tasks: tests.yml + remote_user: '{{ remote_unprivileged_user }}' + + - import_tasks: acls.yml + when: ansible_system == 'Linux' + + - import_tasks: no_log.yml + + - import_tasks: check_mode.yml + + # https://github.com/ansible/ansible/issues/57618 + - name: Test diff contents + copy: + content: 'Ansible managed\n' + dest: "{{ local_temp_dir }}/file.txt" + diff: yes + register: diff_output + + - assert: + that: + - 'diff_output.diff[0].before == ""' + - '"Ansible managed" in diff_output.diff[0].after' + + always: + - name: Cleaning + file: + path: '{{ local_temp_dir }}' + state: absent + delegate_to: localhost + + - name: Remove symbolic link + file: + path: '{{ role_path }}/files/subdir/subdir1/{{ item.key }}' + state: absent + delegate_to: localhost + with_dict: "{{ symlinks }}" + + - name: Remote unprivileged remote user + user: + name: '{{ remote_unprivileged_user }}' + state: absent + remove: yes + + - name: Remove sudoers.d file + file: + path: "{{ sudoers_d_file }}" + state: absent diff --git a/test/integration/targets/copy/tasks/no_log.yml b/test/integration/targets/copy/tasks/no_log.yml new file mode 100644 index 00000000..980c3177 --- /dev/null +++ b/test/integration/targets/copy/tasks/no_log.yml @@ -0,0 +1,82 @@ +- block: + + - set_fact: + dest: "{{ local_temp_dir }}/test_no_log" + + - name: ensure playbook and dest files don't exist yet + file: + path: "{{ item }}" + state: absent + loop: + - "{{ local_temp_dir }}/test_no_log.yml" + - "{{ dest }}" + + - name: create a playbook to run with command + copy: + dest: "{{local_temp_dir}}/test_no_log.yml" + content: !unsafe | + --- + - hosts: localhost + gather_facts: no + tasks: + - copy: + dest: "{{ dest }}" + content: "{{ secret }}" + + - name: copy the secret while using -vvv and check mode + command: "ansible-playbook {{local_temp_dir}}/test_no_log.yml -vvv -e secret=SECRET -e dest={{dest}} --check" + register: result + + - assert: + that: + - "'SECRET' not in result.stdout" + + - name: copy the secret while using -vvv + command: "ansible-playbook {{local_temp_dir}}/test_no_log.yml -vvv -e secret=SECRET -e dest={{dest}}" + register: result + + - assert: + that: + - "'SECRET' not in result.stdout" + + - name: copy the secret while using -vvv and check mode again + command: "ansible-playbook {{local_temp_dir}}/test_no_log.yml -vvv -e secret=SECRET -e dest={{dest}} --check" + register: result + + - assert: + that: + - "'SECRET' not in result.stdout" + + - name: copy the secret while using -vvv again + command: "ansible-playbook {{local_temp_dir}}/test_no_log.yml -vvv -e secret=SECRET -e dest={{dest}}" + register: result + + - assert: + that: + - "'SECRET' not in result.stdout" + + - name: copy a new secret while using -vvv and check mode + command: "ansible-playbook {{local_temp_dir}}/test_no_log.yml -vvv -e secret=NEWSECRET -e dest={{dest}} --check" + register: result + + - assert: + that: + - "'NEWSECRET' not in result.stdout" + + - name: copy a new secret while using -vvv + command: "ansible-playbook {{local_temp_dir}}/test_no_log.yml -vvv -e secret=NEWSECRET -e dest={{dest}}" + register: result + + - assert: + that: + - "'NEWSECRET' not in result.stdout" + + always: + + - name: remove temp test files + file: + path: "{{ item }}" + state: absent + loop: + - "{{ local_temp_dir }}/test_no_log.yml" + - "{{ dest }}" diff --git a/test/integration/targets/copy/tasks/selinux.yml b/test/integration/targets/copy/tasks/selinux.yml new file mode 100644 index 00000000..6bd3b04f --- /dev/null +++ b/test/integration/targets/copy/tasks/selinux.yml @@ -0,0 +1,35 @@ +# Ensure that our logic for special filesystems works as intended +# https://github.com/ansible/ansible/issues/70244 +- block: + - name: Install dosfstools + yum: + name: dosfstools + state: present + + - name: Create a file to use for a fat16 filesystem + command: dd if=/dev/zero of=/fat16 bs=1024 count=10240 + + - name: mkfs.fat + command: mkfs.fat -F16 /fat16 + + - name: Mount it + command: mount /fat16 /mnt + + - name: Copy a file to it + copy: + src: /etc/fstab + dest: /mnt/fstab + always: + - name: Unmount it + command: umount /mnt + ignore_errors: true + + - name: Nuke /fat16 + file: + path: /fat16 + state: absent + + - name: Uninstall dosfstools + yum: + name: dosfstools + state: absent diff --git a/test/integration/targets/copy/tasks/src_file_dest_file_in_non_existent_dir.yml b/test/integration/targets/copy/tasks/src_file_dest_file_in_non_existent_dir.yml new file mode 100644 index 00000000..f4ab9998 --- /dev/null +++ b/test/integration/targets/copy/tasks/src_file_dest_file_in_non_existent_dir.yml @@ -0,0 +1,26 @@ +- name: Ensure that dest top directory doesn't exist + file: + path: '{{ remote_dir }}/{{ dest.split("/")[0] }}' + state: absent + +- name: Copy file, dest is a file in non-existing target directory + copy: + src: foo.txt + dest: '{{ remote_dir }}/{{ dest }}' + register: copy_result + ignore_errors: True + +- name: Assert copy failed + assert: + that: + - 'copy_result is failed' + +- name: Stat dest path + stat: + path: '{{ remote_dir }}/{{ dest.split("/")[0] }}' + register: stat_file_in_dir_result + +- name: assert that dest doesn't exist + assert: + that: + - 'not stat_file_in_dir_result.stat.exists' diff --git a/test/integration/targets/copy/tasks/src_file_dest_file_in_non_existent_dir_remote_src.yml b/test/integration/targets/copy/tasks/src_file_dest_file_in_non_existent_dir_remote_src.yml new file mode 100644 index 00000000..61d87969 --- /dev/null +++ b/test/integration/targets/copy/tasks/src_file_dest_file_in_non_existent_dir_remote_src.yml @@ -0,0 +1,32 @@ +- name: Ensure that dest top directory doesn't exist + file: + path: '{{ remote_dir }}/{{ dest.split("/")[0] }}' + state: absent + +- name: create src file + file: + path: foo.txt + state: touch + +- name: Copy file, dest is a file in non-existing target directory + copy: + src: foo.txt + dest: '{{ remote_dir }}/{{ dest }}' + remote_src: true + register: copy_result + ignore_errors: True + +- name: Assert copy failed + assert: + that: + - 'copy_result is failed' + +- name: Stat dest path + stat: + path: '{{ remote_dir }}/{{ dest.split("/")[0] }}' + register: stat_file_in_dir_result + +- name: assert that dest doesn't exist + assert: + that: + - 'not stat_file_in_dir_result.stat.exists' diff --git a/test/integration/targets/copy/tasks/tests.yml b/test/integration/targets/copy/tasks/tests.yml new file mode 100644 index 00000000..be955317 --- /dev/null +++ b/test/integration/targets/copy/tasks/tests.yml @@ -0,0 +1,2261 @@ +# test code for the copy module and action plugin +# (c) 2014, Michael DeHaan <michael.dehaan@gmail.com> +# (c) 2017, Ansible Project +# +# GNU General Public License v3 or later (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt ) +# + +- name: Record the output directory + set_fact: + remote_file: "{{ remote_dir }}/foo.txt" + +- name: Initiate a basic copy, and also test the mode + copy: + src: foo.txt + dest: "{{ remote_file }}" + mode: 0444 + register: copy_result + +- name: Record the sha of the test file for later tests + set_fact: + remote_file_hash: "{{ copy_result['checksum'] }}" + +- name: Check the mode of the output file + file: + name: "{{ remote_file }}" + state: file + register: file_result_check + +- name: Assert the mode is correct + assert: + that: + - "file_result_check.mode == '0444'" + +# same as expanduser & expandvars +- command: 'echo {{ remote_dir }}' + register: echo + +- set_fact: + remote_dir_expanded: '{{ echo.stdout }}' + remote_file_expanded: '{{ echo.stdout }}/foo.txt' + +- debug: + var: copy_result + verbosity: 1 + +- name: Assert basic copy worked + assert: + that: + - "'changed' in copy_result" + - copy_result.dest == remote_file_expanded + - "'group' in copy_result" + - "'gid' in copy_result" + - "'checksum' in copy_result" + - "'owner' in copy_result" + - "'size' in copy_result" + - "'src' in copy_result" + - "'state' in copy_result" + - "'uid' in copy_result" + +- name: Verify that the file was marked as changed + assert: + that: + - "copy_result.changed == true" + +- name: Verify that the file checksums are correct + assert: + that: + - "copy_result.checksum == ('foo.txt\n'|hash('sha1'))" + +- name: Verify that the legacy md5sum is correct + assert: + that: + - "copy_result.md5sum == ('foo.txt\n'|hash('md5'))" + when: ansible_fips|bool != True + +- name: Check the stat results of the file + stat: + path: "{{ remote_file }}" + register: stat_results + +- debug: + var: stat_results + verbosity: 1 + +- name: Assert the stat results are correct + assert: + that: + - "stat_results.stat.exists == true" + - "stat_results.stat.isblk == false" + - "stat_results.stat.isfifo == false" + - "stat_results.stat.isreg == true" + - "stat_results.stat.issock == false" + - "stat_results.stat.checksum == ('foo.txt\n'|hash('sha1'))" + +- name: Overwrite the file via same means + copy: + src: foo.txt + dest: "{{ remote_file }}" + decrypt: no + register: copy_result2 + +- name: Assert that the file was not changed + assert: + that: + - "copy_result2 is not changed" + +- name: Assert basic copy worked + assert: + that: + - "'changed' in copy_result2" + - copy_result2.dest == remote_file_expanded + - "'group' in copy_result2" + - "'gid' in copy_result2" + - "'checksum' in copy_result2" + - "'owner' in copy_result2" + - "'size' in copy_result2" + - "'state' in copy_result2" + - "'uid' in copy_result2" + +- name: Overwrite the file using the content system + copy: + content: "modified" + dest: "{{ remote_file }}" + decrypt: no + register: copy_result3 + +- name: Check the stat results of the file + stat: + path: "{{ remote_file }}" + register: stat_results + +- debug: + var: stat_results + verbosity: 1 + +- name: Assert that the file has changed + assert: + that: + - "copy_result3 is changed" + - "'content' not in copy_result3" + - "stat_results.stat.checksum == ('modified'|hash('sha1'))" + - "stat_results.stat.mode != '0700'" + +- name: Overwrite the file again using the content system, also passing along file params + copy: + content: "modified" + dest: "{{ remote_file }}" + mode: 0700 + decrypt: no + register: copy_result4 + +- name: Check the stat results of the file + stat: + path: "{{ remote_file }}" + register: stat_results + +- debug: + var: stat_results + verbosity: 1 + +- name: Assert that the file has changed + assert: + that: + - "copy_result3 is changed" + - "'content' not in copy_result3" + - "stat_results.stat.checksum == ('modified'|hash('sha1'))" + - "stat_results.stat.mode == '0700'" + +- name: Create a hardlink to the file + file: + src: '{{ remote_file }}' + dest: '{{ remote_dir }}/hard.lnk' + state: hard + +- name: copy the same contents into place + copy: + content: 'modified' + dest: '{{ remote_file }}' + mode: 0700 + decrypt: no + register: copy_results + +- name: Check the stat results of the file + stat: + path: "{{ remote_file }}" + register: stat_results + +- name: Check the stat results of the hard link + stat: + path: "{{ remote_dir }}/hard.lnk" + register: hlink_results + +- name: Check that the file did not change + assert: + that: + - 'stat_results.stat.inode == hlink_results.stat.inode' + - 'copy_results.changed == False' + - "stat_results.stat.checksum == ('modified'|hash('sha1'))" + +- name: copy the same contents into place but change mode + copy: + content: 'modified' + dest: '{{ remote_file }}' + mode: 0404 + decrypt: no + register: copy_results + +- name: Check the stat results of the file + stat: + path: "{{ remote_file }}" + register: stat_results + +- name: Check the stat results of the hard link + stat: + path: "{{ remote_dir }}/hard.lnk" + register: hlink_results + +- name: Check that the file changed permissions but is still the same + assert: + that: + - 'stat_results.stat.inode == hlink_results.stat.inode' + - 'copy_results.changed == True' + - 'stat_results.stat.mode == hlink_results.stat.mode' + - 'stat_results.stat.mode == "0404"' + - "stat_results.stat.checksum == ('modified'|hash('sha1'))" + +- name: copy the different contents into place + copy: + content: 'adjusted' + dest: '{{ remote_file }}' + mode: 0404 + register: copy_results + +- name: Check the stat results of the file + stat: + path: "{{ remote_file }}" + register: stat_results + +- name: Check the stat results of the hard link + stat: + path: "{{ remote_dir }}/hard.lnk" + register: hlink_results + +- name: Check that the file changed and hardlink was broken + assert: + that: + - 'stat_results.stat.inode != hlink_results.stat.inode' + - 'copy_results.changed == True' + - "stat_results.stat.checksum == ('adjusted'|hash('sha1'))" + - "hlink_results.stat.checksum == ('modified'|hash('sha1'))" + +- name: Try invalid copy input location fails + copy: + src: invalid_file_location_does_not_exist + dest: "{{ remote_dir }}/file.txt" + ignore_errors: True + register: failed_copy + +- name: Assert that invalid source failed + assert: + that: + - "failed_copy.failed" + - "'invalid_file_location_does_not_exist' in failed_copy.msg" + +- name: Try empty source to ensure it fails + copy: + src: '' + dest: "{{ remote_dir }}" + ignore_errors: True + register: failed_copy + +- debug: + var: failed_copy + verbosity: 1 + +- name: Assert that empty source failed + assert: + that: + - failed_copy is failed + - "'src (or content) is required' in failed_copy.msg" + +- name: Try without destination to ensure it fails + copy: + src: foo.txt + ignore_errors: True + register: failed_copy + +- debug: + var: failed_copy + verbosity: 1 + +- name: Assert that missing destination failed + assert: + that: + - failed_copy is failed + - "'dest is required' in failed_copy.msg" + +- name: Try without source to ensure it fails + copy: + dest: "{{ remote_file }}" + ignore_errors: True + register: failed_copy + +- debug: + var: failed_copy + verbosity: 1 + +- name: Assert that missing source failed + assert: + that: + - failed_copy is failed + - "'src (or content) is required' in failed_copy.msg" + +- name: Try with both src and content to ensure it fails + copy: + src: foo.txt + content: testing + dest: "{{ remote_file }}" + ignore_errors: True + register: failed_copy + +- name: Assert that mutually exclusive parameters failed + assert: + that: + - failed_copy is failed + - "'mutually exclusive' in failed_copy.msg" + +- name: Try with content and directory as destination to ensure it fails + copy: + content: testing + dest: "{{ remote_dir }}" + ignore_errors: True + register: failed_copy + +- debug: + var: failed_copy + verbosity: 1 + +- name: Assert that content and directory as destination failed + assert: + that: + - failed_copy is failed + - "'can not use content with a dir as dest' in failed_copy.msg" + +- name: Clean up + file: + path: "{{ remote_file }}" + state: absent + +- name: Copy source file to destination directory with mode + copy: + src: foo.txt + dest: "{{ remote_dir }}" + mode: 0500 + register: copy_results + +- name: Check the stat results of the file + stat: + path: '{{ remote_file }}' + register: stat_results + +- debug: + var: stat_results + verbosity: 1 + +- name: Assert that the file has changed + assert: + that: + - "copy_results is changed" + - "stat_results.stat.checksum == ('foo.txt\n'|hash('sha1'))" + - "stat_results.stat.mode == '0500'" + +# Test copy with mode=preserve +- name: Create file and set perms to an odd value + copy: + content: "foo.txt\n" + dest: '{{ local_temp_dir }}/foo.txt' + mode: 0547 + delegate_to: localhost + +- name: Copy with mode=preserve + copy: + src: '{{ local_temp_dir }}/foo.txt' + dest: '{{ remote_dir }}/copy-foo.txt' + mode: preserve + register: copy_results + +- name: Check the stat results of the file + stat: + path: '{{ remote_dir }}/copy-foo.txt' + register: stat_results + +- name: Assert that the file has changed and has correct mode + assert: + that: + - "copy_results is changed" + - "copy_results.mode == '0547'" + - "stat_results.stat.checksum == ('foo.txt\n'|hash('sha1'))" + - "stat_results.stat.mode == '0547'" + +- name: Test copy with mode=preserve and remote_src=True + copy: + src: '{{ remote_dir }}/copy-foo.txt' + dest: '{{ remote_dir }}/copy-foo2.txt' + mode: 'preserve' + remote_src: True + register: copy_results2 + +- name: Check the stat results of the file + stat: + path: '{{ remote_dir }}/copy-foo2.txt' + register: stat_results2 + +- name: Assert that the file has changed and has correct mode + assert: + that: + - "copy_results2 is changed" + - "copy_results2.mode == '0547'" + - "stat_results2.stat.checksum == ('foo.txt\n'|hash('sha1'))" + - "stat_results2.stat.mode == '0547'" + +# +# test recursive copy local_follow=False, no trailing slash +# + +- name: Create empty directory in the role we're copying from (git can't store empty dirs) + file: + path: '{{ role_path }}/files/subdir/subdira' + state: directory + delegate_to: localhost + +- name: Set the output subdirectory + set_fact: + remote_subdir: "{{ remote_dir }}/sub" + +- name: Make an output subdirectory + file: + name: "{{ remote_subdir }}" + state: directory + +- name: Setup link target for absolute link + copy: + dest: /tmp/ansible-test-abs-link + content: target + delegate_to: localhost + +- name: Setup link target dir for absolute link + file: + dest: /tmp/ansible-test-abs-link-dir + state: directory + delegate_to: localhost + +- name: Test recursive copy to directory no trailing slash, local_follow=False + copy: + src: subdir + dest: "{{ remote_subdir }}" + directory_mode: 0700 + local_follow: False + register: recursive_copy_result + +- debug: + var: recursive_copy_result + verbosity: 1 + +- name: Assert that the recursive copy did something + assert: + that: + - "recursive_copy_result is changed" + +- name: Check that a file in a directory was transferred + stat: + path: "{{ remote_dir }}/sub/subdir/bar.txt" + register: stat_bar + +- name: Check that a file in a deeper directory was transferred + stat: + path: "{{ remote_dir }}/sub/subdir/subdir2/baz.txt" + register: stat_bar2 + +- name: Check that a file in a directory whose parent contains a directory alone was transferred + stat: + path: "{{ remote_dir }}/sub/subdir/subdir2/subdir3/subdir4/qux.txt" + register: stat_bar3 + +- name: Assert recursive copy files + assert: + that: + - "stat_bar.stat.exists" + - "stat_bar2.stat.exists" + - "stat_bar3.stat.exists" + +- name: Check symlink to absolute path + stat: + path: '{{ remote_dir }}/sub/subdir/subdir1/ansible-test-abs-link' + register: stat_abs_link + +- name: Check symlink to relative path + stat: + path: '{{ remote_dir }}/sub/subdir/subdir1/bar.txt' + register: stat_relative_link + +- name: Check symlink to self + stat: + path: '{{ remote_dir }}/sub/subdir/subdir1/invalid' + register: stat_self_link + +- name: Check symlink to nonexistent file + stat: + path: '{{ remote_dir }}/sub/subdir/subdir1/invalid2' + register: stat_invalid_link + +- name: Check symlink to directory in copy + stat: + path: '{{ remote_dir }}/sub/subdir/subdir1/subdir3' + register: stat_dir_in_copy_link + +- name: Check symlink to directory outside of copy + stat: + path: '{{ remote_dir }}/sub/subdir/subdir1/ansible-test-abs-link-dir' + register: stat_dir_outside_copy_link + +- name: Assert recursive copy symlinks local_follow=False + assert: + that: + - "stat_abs_link.stat.exists" + - "stat_abs_link.stat.islnk" + - "'/tmp/ansible-test-abs-link' == stat_abs_link.stat.lnk_target" + - "stat_relative_link.stat.exists" + - "stat_relative_link.stat.islnk" + - "'../bar.txt' == stat_relative_link.stat.lnk_target" + - "stat_self_link.stat.exists" + - "stat_self_link.stat.islnk" + - "'invalid' in stat_self_link.stat.lnk_target" + - "stat_invalid_link.stat.exists" + - "stat_invalid_link.stat.islnk" + - "'../invalid' in stat_invalid_link.stat.lnk_target" + - "stat_dir_in_copy_link.stat.exists" + - "stat_dir_in_copy_link.stat.islnk" + - "'../subdir2/subdir3' in stat_dir_in_copy_link.stat.lnk_target" + - "stat_dir_outside_copy_link.stat.exists" + - "stat_dir_outside_copy_link.stat.islnk" + - "'/tmp/ansible-test-abs-link-dir' == stat_dir_outside_copy_link.stat.lnk_target" + +- name: Stat the recursively copied directories + stat: + path: "{{ remote_dir }}/sub/{{ item }}" + register: dir_stats + with_items: + - "subdir" + - "subdir/subdira" + - "subdir/subdir1" + - "subdir/subdir2" + - "subdir/subdir2/subdir3" + - "subdir/subdir2/subdir3/subdir4" + +- debug: + var: stat_results + verbosity: 1 + +- name: Assert recursive copied directories mode (1) + assert: + that: + - "item.stat.exists" + - "item.stat.mode == '0700'" + with_items: "{{dir_stats.results}}" + +- name: Test recursive copy to directory no trailing slash, local_follow=False second time + copy: + src: subdir + dest: "{{ remote_subdir }}" + directory_mode: 0700 + local_follow: False + register: recursive_copy_result + +- name: Assert that the second copy did not change anything + assert: + that: + - "recursive_copy_result is not changed" + +- name: Cleanup the recursive copy subdir + file: + name: "{{ remote_subdir }}" + state: absent + +# +# Recursive copy with local_follow=False, trailing slash +# + +- name: Set the output subdirectory + set_fact: + remote_subdir: "{{ remote_dir }}/sub" + +- name: Make an output subdirectory + file: + name: "{{ remote_subdir }}" + state: directory + +- name: Setup link target for absolute link + copy: + dest: /tmp/ansible-test-abs-link + content: target + delegate_to: localhost + +- name: Setup link target dir for absolute link + file: + dest: /tmp/ansible-test-abs-link-dir + state: directory + delegate_to: localhost + +- name: Test recursive copy to directory trailing slash, local_follow=False + copy: + src: subdir/ + dest: "{{ remote_subdir }}" + directory_mode: 0700 + local_follow: False + register: recursive_copy_result + +- debug: + var: recursive_copy_result + verbosity: 1 + +- name: Assert that the recursive copy did something + assert: + that: + - "recursive_copy_result is changed" + +- name: Check that a file in a directory was transferred + stat: + path: "{{ remote_dir }}/sub/bar.txt" + register: stat_bar + +- name: Check that a file in a deeper directory was transferred + stat: + path: "{{ remote_dir }}/sub/subdir2/baz.txt" + register: stat_bar2 + +- name: Check that a file in a directory whose parent contains a directory alone was transferred + stat: + path: "{{ remote_dir }}/sub/subdir2/subdir3/subdir4/qux.txt" + register: stat_bar3 + +- name: Assert recursive copy files + assert: + that: + - "stat_bar.stat.exists" + - "stat_bar2.stat.exists" + - "stat_bar3.stat.exists" + +- name: Check symlink to absolute path + stat: + path: '{{ remote_dir }}/sub/subdir1/ansible-test-abs-link' + register: stat_abs_link + +- name: Check symlink to relative path + stat: + path: '{{ remote_dir }}/sub/subdir1/bar.txt' + register: stat_relative_link + +- name: Check symlink to self + stat: + path: '{{ remote_dir }}/sub/subdir1/invalid' + register: stat_self_link + +- name: Check symlink to nonexistent file + stat: + path: '{{ remote_dir }}/sub/subdir1/invalid2' + register: stat_invalid_link + +- name: Check symlink to directory in copy + stat: + path: '{{ remote_dir }}/sub/subdir1/subdir3' + register: stat_dir_in_copy_link + +- name: Check symlink to directory outside of copy + stat: + path: '{{ remote_dir }}/sub/subdir1/ansible-test-abs-link-dir' + register: stat_dir_outside_copy_link + +- name: Assert recursive copy symlinks local_follow=False trailing slash + assert: + that: + - "stat_abs_link.stat.exists" + - "stat_abs_link.stat.islnk" + - "'/tmp/ansible-test-abs-link' == stat_abs_link.stat.lnk_target" + - "stat_relative_link.stat.exists" + - "stat_relative_link.stat.islnk" + - "'../bar.txt' == stat_relative_link.stat.lnk_target" + - "stat_self_link.stat.exists" + - "stat_self_link.stat.islnk" + - "'invalid' in stat_self_link.stat.lnk_target" + - "stat_invalid_link.stat.exists" + - "stat_invalid_link.stat.islnk" + - "'../invalid' in stat_invalid_link.stat.lnk_target" + - "stat_dir_in_copy_link.stat.exists" + - "stat_dir_in_copy_link.stat.islnk" + - "'../subdir2/subdir3' in stat_dir_in_copy_link.stat.lnk_target" + - "stat_dir_outside_copy_link.stat.exists" + - "stat_dir_outside_copy_link.stat.islnk" + - "'/tmp/ansible-test-abs-link-dir' == stat_dir_outside_copy_link.stat.lnk_target" + +- name: Stat the recursively copied directories + stat: + path: "{{ remote_dir }}/sub/{{ item }}" + register: dir_stats + with_items: + - "subdira" + - "subdir1" + - "subdir2" + - "subdir2/subdir3" + - "subdir2/subdir3/subdir4" + +- debug: + var: dir_stats + verbosity: 1 + +- name: Assert recursive copied directories mode (2) + assert: + that: + - "item.stat.mode == '0700'" + with_items: "{{dir_stats.results}}" + +- name: Test recursive copy to directory trailing slash, local_follow=False second time + copy: + src: subdir/ + dest: "{{ remote_subdir }}" + directory_mode: 0700 + local_follow: False + register: recursive_copy_result + +- name: Assert that the second copy did not change anything + assert: + that: + - "recursive_copy_result is not changed" + +- name: Cleanup the recursive copy subdir + file: + name: "{{ remote_subdir }}" + state: absent + +# +# test recursive copy local_follow=True, no trailing slash +# + +- name: Set the output subdirectory + set_fact: + remote_subdir: "{{ remote_dir }}/sub" + +- name: Make an output subdirectory + file: + name: "{{ remote_subdir }}" + state: directory + +- name: Setup link target for absolute link + copy: + dest: /tmp/ansible-test-abs-link + content: target + delegate_to: localhost + +- name: Setup link target dir for absolute link + file: + dest: /tmp/ansible-test-abs-link-dir + state: directory + delegate_to: localhost + +- name: Test recursive copy to directory no trailing slash, local_follow=True + copy: + src: subdir + dest: "{{ remote_subdir }}" + directory_mode: 0700 + local_follow: True + register: recursive_copy_result + +- debug: + var: recursive_copy_result + verbosity: 1 + +- name: Assert that the recursive copy did something + assert: + that: + - "recursive_copy_result is changed" + +- name: Check that a file in a directory was transferred + stat: + path: "{{ remote_dir }}/sub/subdir/bar.txt" + register: stat_bar + +- name: Check that a file in a deeper directory was transferred + stat: + path: "{{ remote_dir }}/sub/subdir/subdir2/baz.txt" + register: stat_bar2 + +- name: Check that a file in a directory whose parent contains a directory alone was transferred + stat: + path: "{{ remote_dir }}/sub/subdir/subdir2/subdir3/subdir4/qux.txt" + register: stat_bar3 + +- name: Check that a file in a directory whose parent is a symlink was transferred + stat: + path: "{{ remote_dir }}/sub/subdir/subdir1/subdir3/subdir4/qux.txt" + register: stat_bar4 + +- name: Assert recursive copy files + assert: + that: + - "stat_bar.stat.exists" + - "stat_bar2.stat.exists" + - "stat_bar3.stat.exists" + - "stat_bar4.stat.exists" + +- name: Check symlink to absolute path + stat: + path: '{{ remote_dir }}/sub/subdir/subdir1/ansible-test-abs-link' + register: stat_abs_link + +- name: Check symlink to relative path + stat: + path: '{{ remote_dir }}/sub/subdir/subdir1/bar.txt' + register: stat_relative_link + +- name: Check symlink to self + stat: + path: '{{ remote_dir }}/sub/subdir/subdir1/invalid' + register: stat_self_link + +- name: Check symlink to nonexistent file + stat: + path: '{{ remote_dir }}/sub/subdir/subdir1/invalid2' + register: stat_invalid_link + +- name: Check symlink to directory in copy + stat: + path: '{{ remote_dir }}/sub/subdir/subdir1/subdir3' + register: stat_dir_in_copy_link + +- name: Check symlink to directory outside of copy + stat: + path: '{{ remote_dir }}/sub/subdir/subdir1/ansible-test-abs-link-dir' + register: stat_dir_outside_copy_link + +- name: Assert recursive copy symlinks local_follow=True + assert: + that: + - "stat_abs_link.stat.exists" + - "not stat_abs_link.stat.islnk" + - "stat_abs_link.stat.checksum == ('target'|hash('sha1'))" + - "stat_relative_link.stat.exists" + - "not stat_relative_link.stat.islnk" + - "stat_relative_link.stat.checksum == ('baz\n'|hash('sha1'))" + - "stat_self_link.stat.exists" + - "stat_self_link.stat.islnk" + - "'invalid' in stat_self_link.stat.lnk_target" + - "stat_invalid_link.stat.exists" + - "stat_invalid_link.stat.islnk" + - "'../invalid' in stat_invalid_link.stat.lnk_target" + - "stat_dir_in_copy_link.stat.exists" + - "not stat_dir_in_copy_link.stat.islnk" + - "stat_dir_in_copy_link.stat.isdir" + - + - "stat_dir_outside_copy_link.stat.exists" + - "not stat_dir_outside_copy_link.stat.islnk" + - "stat_dir_outside_copy_link.stat.isdir" + +- name: Stat the recursively copied directories + stat: + path: "{{ remote_dir }}/sub/{{ item }}" + register: dir_stats + with_items: + - "subdir" + - "subdir/subdira" + - "subdir/subdir1" + - "subdir/subdir1/subdir3" + - "subdir/subdir1/subdir3/subdir4" + - "subdir/subdir2" + - "subdir/subdir2/subdir3" + - "subdir/subdir2/subdir3/subdir4" + +- debug: + var: dir_stats + verbosity: 1 + +- name: Assert recursive copied directories mode (3) + assert: + that: + - "item.stat.mode == '0700'" + with_items: "{{dir_stats.results}}" + +- name: Test recursive copy to directory no trailing slash, local_follow=True second time + copy: + src: subdir + dest: "{{ remote_subdir }}" + directory_mode: 0700 + local_follow: True + register: recursive_copy_result + +- name: Assert that the second copy did not change anything + assert: + that: + - "recursive_copy_result is not changed" + +- name: Cleanup the recursive copy subdir + file: + name: "{{ remote_subdir }}" + state: absent + +# +# Recursive copy of tricky symlinks +# +- block: + - name: Create a directory to copy from + file: + path: '{{ local_temp_dir }}/source1' + state: directory + + - name: Create a directory outside of the tree + file: + path: '{{ local_temp_dir }}/source2' + state: directory + + - name: Create a symlink to a directory outside of the tree + file: + path: '{{ local_temp_dir }}/source1/link' + src: '{{ local_temp_dir }}/source2' + state: link + + - name: Create a circular link back to the tree + file: + path: '{{ local_temp_dir }}/source2/circle' + src: '../source1' + state: link + + - name: Create output directory + file: + path: '{{ local_temp_dir }}/dest1' + state: directory + delegate_to: localhost + +- name: Recursive copy the source + copy: + src: '{{ local_temp_dir }}/source1' + dest: '{{ remote_dir }}/dest1' + local_follow: True + register: copy_result + +- name: Check that the tree link is now a directory + stat: + path: '{{ remote_dir }}/dest1/source1/link' + register: link_result + +- name: Check that the out of tree link is still a link + stat: + path: '{{ remote_dir }}/dest1/source1/link/circle' + register: circle_result + +- name: Verify that the recursive copy worked + assert: + that: + - 'copy_result.changed' + - 'link_result.stat.isdir' + - 'not link_result.stat.islnk' + - 'circle_result.stat.islnk' + - '"../source1" == circle_result.stat.lnk_target' + +- name: Recursive copy the source a second time + copy: + src: '{{ local_temp_dir }}/source1' + dest: '{{ remote_dir }}/dest1' + local_follow: True + register: copy_result + +- name: Verify that the recursive copy made no changes + assert: + that: + - 'not copy_result.changed' + +# +# Recursive copy with absolute paths (#27439) +# +- name: Test that remote_dir is appropriate for this test (absolute path) + assert: + that: + - '{{ remote_dir_expanded[0] == "/" }}' + +- block: + - name: Create a directory to copy + file: + path: '{{ local_temp_dir }}/source_recursive' + state: directory + + - name: Create a file inside of the directory + copy: + content: "testing" + dest: '{{ local_temp_dir }}/source_recursive/file' + + - name: Create a directory to place the test output in + file: + path: '{{ local_temp_dir }}/destination' + state: directory + delegate_to: localhost + +- name: Copy the directory and files within (no trailing slash) + copy: + src: '{{ local_temp_dir }}/source_recursive' + dest: '{{ remote_dir }}/destination' + +- name: Stat the recursively copied directory + stat: + path: "{{ remote_dir }}/destination/{{ item }}" + register: copied_stat + with_items: + - "source_recursive" + - "source_recursive/file" + - "file" + +- debug: + var: copied_stat + verbosity: 1 + +- name: Assert with no trailing slash, directory and file is copied + assert: + that: + - "copied_stat.results[0].stat.exists" + - "copied_stat.results[1].stat.exists" + - "not copied_stat.results[2].stat.exists" + +- name: Cleanup + file: + path: '{{ remote_dir }}/destination' + state: absent + +# Try again with no trailing slash + +- name: Create a directory to place the test output in + file: + path: '{{ remote_dir }}/destination' + state: directory + +- name: Copy just the files inside of the directory + copy: + src: '{{ local_temp_dir }}/source_recursive/' + dest: '{{ remote_dir }}/destination' + +- name: Stat the recursively copied directory + stat: + path: "{{ remote_dir }}/destination/{{ item }}" + register: copied_stat + with_items: + - "source_recursive" + - "source_recursive/file" + - "file" + +- debug: + var: copied_stat + verbosity: 1 + +- name: Assert with trailing slash, only the file is copied + assert: + that: + - "not copied_stat.results[0].stat.exists" + - "not copied_stat.results[1].stat.exists" + - "copied_stat.results[2].stat.exists" + +# +# Recursive copy with relative paths (#34893) +# + +- name: Create a directory to copy + file: + path: 'source_recursive' + state: directory + delegate_to: localhost + +- name: Create a file inside of the directory + copy: + content: "testing" + dest: 'source_recursive/file' + delegate_to: localhost + +- name: Create a directory to place the test output in + file: + path: 'destination' + state: directory + delegate_to: localhost + +- name: Copy the directory and files within (no trailing slash) + copy: + src: 'source_recursive' + dest: 'destination' + +- name: Stat the recursively copied directory + stat: + path: "destination/{{ item }}" + register: copied_stat + with_items: + - "source_recursive" + - "source_recursive/file" + - "file" + +- debug: + var: copied_stat + verbosity: 1 + +- name: Assert with no trailing slash, directory and file is copied + assert: + that: + - "copied_stat.results[0].stat.exists" + - "copied_stat.results[1].stat.exists" + - "not copied_stat.results[2].stat.exists" + +- name: Cleanup + file: + path: 'destination' + state: absent + +# Try again with no trailing slash + +- name: Create a directory to place the test output in + file: + path: 'destination' + state: directory + +- name: Copy just the files inside of the directory + copy: + src: 'source_recursive/' + dest: 'destination' + +- name: Stat the recursively copied directory + stat: + path: "destination/{{ item }}" + register: copied_stat + with_items: + - "source_recursive" + - "source_recursive/file" + - "file" + +- debug: + var: copied_stat + verbosity: 1 + +- name: Assert with trailing slash, only the file is copied + assert: + that: + - "not copied_stat.results[0].stat.exists" + - "not copied_stat.results[1].stat.exists" + - "copied_stat.results[2].stat.exists" + +- name: Cleanup + file: + path: 'destination' + state: absent + +- name: Cleanup + file: + path: 'source_recursive' + state: absent + +# +# issue 8394 +# + +- name: Create a file with content and a literal multiline block + copy: + content: | + this is the first line + this is the second line + + this line is after an empty line + this line is the last line + dest: "{{ remote_dir }}/multiline.txt" + register: copy_result6 + +- debug: + var: copy_result6 + verbosity: 1 + +- name: Assert the multiline file was created correctly + assert: + that: + - "copy_result6.changed" + - "copy_result6.dest == '{{remote_dir_expanded}}/multiline.txt'" + - "copy_result6.checksum == '9cd0697c6a9ff6689f0afb9136fa62e0b3fee903'" + +# test overwriting a file as an unprivileged user (pull request #8624) +# this can't be relative to {{remote_dir}} as ~root usually has mode 700 +- block: + - name: Create world writable directory + file: + dest: /tmp/worldwritable + state: directory + mode: 0777 + + - name: Create world writable file + copy: + dest: /tmp/worldwritable/file.txt + content: "bar" + mode: 0666 + + - name: Overwrite the file as user nobody + copy: + dest: /tmp/worldwritable/file.txt + content: "baz" + become: yes + become_user: nobody + register: copy_result7 + + - name: Assert the file was overwritten + assert: + that: + - "copy_result7.changed" + - "copy_result7.dest == '/tmp/worldwritable/file.txt'" + - "copy_result7.checksum == ('baz'|hash('sha1'))" + + - name: Clean up + file: + dest: /tmp/worldwritable + state: absent + + remote_user: root + +# +# Follow=True tests +# + +# test overwriting a link using "follow=yes" so that the link +# is preserved and the link target is updated + +- name: Create a test file to symlink to + copy: + dest: "{{ remote_dir }}/follow_test" + content: "this is the follow test file\n" + +- name: Create a symlink to the test file + file: + path: "{{ remote_dir }}/follow_link" + src: './follow_test' + state: link + +- name: Update the test file using follow=True to preserve the link + copy: + dest: "{{ remote_dir }}/follow_link" + src: foo.txt + follow: yes + register: replace_follow_result + +- name: Stat the link path + stat: + path: "{{ remote_dir }}/follow_link" + register: stat_link_result + +- name: Assert that the link is still a link and contents were changed + assert: + that: + - stat_link_result['stat']['islnk'] + - stat_link_result['stat']['lnk_target'] == './follow_test' + - replace_follow_result['changed'] + - "replace_follow_result['checksum'] == remote_file_hash" + +# Symlink handling when the dest is already there +# https://github.com/ansible/ansible-modules-core/issues/1568 + +- name: test idempotency by trying to copy to the symlink with the same contents + copy: + dest: "{{ remote_dir }}/follow_link" + src: foo.txt + follow: yes + register: replace_follow_result + +- name: Stat the link path + stat: + path: "{{ remote_dir }}/follow_link" + register: stat_link_result + +- name: Assert that the link is still a link and contents were changed + assert: + that: + - stat_link_result['stat']['islnk'] + - stat_link_result['stat']['lnk_target'] == './follow_test' + - not replace_follow_result['changed'] + - replace_follow_result['checksum'] == remote_file_hash + + +- name: Update the test file using follow=False to overwrite the link + copy: + dest: '{{ remote_dir }}/follow_link' + content: 'modified' + follow: False + register: copy_results + +- name: Check the stat results of the file + stat: + path: '{{remote_dir}}/follow_link' + register: stat_results + +- debug: + var: stat_results + verbosity: 1 + +- name: Assert that the file has changed and is not a link + assert: + that: + - "copy_results is changed" + - "'content' not in copy_results" + - "stat_results.stat.checksum == ('modified'|hash('sha1'))" + - "not stat_results.stat.islnk" + +# test overwriting a link using "follow=yes" so that the link +# is preserved and the link target is updated when the thing being copied is a link + +# +# File mode tests +# + +- name: setup directory for test + file: state=directory dest={{remote_dir }}/directory mode=0755 + +- name: set file mode when the destination is a directory + copy: src=foo.txt dest={{remote_dir}}/directory/ mode=0705 + +- name: set file mode when the destination is a directory + copy: src=foo.txt dest={{remote_dir}}/directory/ mode=0604 + register: file_result + +- name: check that the file has the correct attributes + stat: path={{ remote_dir }}/directory/foo.txt + register: file_attrs + +- assert: + that: + - "file_attrs.stat.mode == '0604'" + # The below assertions make an invalid assumption, these were not explicitly set + # - "file_attrs.stat.uid == 0" + # - "file_attrs.stat.pw_name == 'root'" + +- name: check that the containing directory did not change attributes + stat: path={{ remote_dir }}/directory/ + register: dir_attrs + +- assert: + that: + - "dir_attrs.stat.mode == '0755'" + +# Test that recursive copy of a directory containing a symlink to another +# directory, with mode=preserve and local_follow=no works. +# See: https://github.com/ansible/ansible/issues/68471 + +- name: Test recursive copy of dir with symlinks, mode=preserve, local_follow=False + copy: + src: '{{ role_path }}/files/subdir/' + dest: '{{ local_temp_dir }}/preserve_symlink/' + mode: preserve + local_follow: no + +- name: check that we actually used and still have a symlink + stat: path={{ local_temp_dir }}/preserve_symlink/subdir1/bar.txt + register: symlink_path + +- assert: + that: + - symlink_path.stat.exists + - symlink_path.stat.islnk + +# +# I believe the below section is now covered in the recursive copying section. +# Hold on for now as an original test case but delete once confirmed that +# everything is passing + +# +# Recursive copying with symlinks tests +# +- delegate_to: localhost + block: + - name: Create a test dir to copy + file: + path: '{{ local_temp_dir }}/top_dir' + state: directory + + - name: Create a test dir to symlink to + file: + path: '{{ local_temp_dir }}/linked_dir' + state: directory + + - name: Create a file in the test dir + copy: + dest: '{{ local_temp_dir }}/linked_dir/file1' + content: 'hello world' + + - name: Create a link to the test dir + file: + path: '{{ local_temp_dir }}/top_dir/follow_link_dir' + src: '{{ local_temp_dir }}/linked_dir' + state: link + + - name: Create a circular subdir + file: + path: '{{ local_temp_dir }}/top_dir/subdir' + state: directory + + ### FIXME: Also add a test for a relative symlink + - name: Create a circular symlink + file: + path: '{{ local_temp_dir }}/top_dir/subdir/circle' + src: '{{ local_temp_dir }}/top_dir/' + state: link + +- name: Copy the directory's link + copy: + src: '{{ local_temp_dir }}/top_dir' + dest: '{{ remote_dir }}/new_dir' + local_follow: True + +- name: Stat the copied path + stat: + path: '{{ remote_dir }}/new_dir/top_dir/follow_link_dir' + register: stat_dir_result + +- name: Stat the copied file + stat: + path: '{{ remote_dir }}/new_dir/top_dir/follow_link_dir/file1' + register: stat_file_in_dir_result + +- name: Stat the circular symlink + stat: + path: '{{ remote_dir }}/new_dir/top_dir/subdir/circle' + register: stat_circular_symlink_result + +- name: Assert that the directory exists + assert: + that: + - stat_dir_result.stat.exists + - stat_dir_result.stat.isdir + - stat_file_in_dir_result.stat.exists + - stat_file_in_dir_result.stat.isreg + - stat_circular_symlink_result.stat.exists + - stat_circular_symlink_result.stat.islnk + +# Relative paths in dest: +- name: Smoketest that copying content to an implicit relative path works + copy: + content: 'testing' + dest: 'ansible-testing.txt' + register: relative_results + +- name: Assert that copying to an implicit relative path reported changed + assert: + that: + - 'relative_results["changed"]' + - 'relative_results["checksum"] == "dc724af18fbdd4e59189f5fe768a5f8311527050"' + +- name: Test that copying the same content with an implicit relative path reports no change + copy: + content: 'testing' + dest: 'ansible-testing.txt' + register: relative_results + +- name: Assert that copying the same content with an implicit relative path reports no change + assert: + that: + - 'not relative_results["changed"]' + - 'relative_results["checksum"] == "dc724af18fbdd4e59189f5fe768a5f8311527050"' + +- name: Test that copying different content with an implicit relative path reports change + copy: + content: 'testing2' + dest: 'ansible-testing.txt' + register: relative_results + +- name: Assert that copying different content with an implicit relative path reports changed + assert: + that: + - 'relative_results["changed"]' + - 'relative_results["checksum"] == "596b29ec9afea9e461a20610d150939b9c399d93"' + +- name: Smoketest that explicit relative path works + copy: + content: 'testing' + dest: './ansible-testing.txt' + register: relative_results + +- name: Assert that explicit relative paths reports change + assert: + that: + - 'relative_results["changed"]' + - 'relative_results["checksum"] == "dc724af18fbdd4e59189f5fe768a5f8311527050"' + +- name: Cleanup relative path tests + file: + path: 'ansible-testing.txt' + state: absent + +# src is a file, dest is a non-existent directory (2 levels of directories): +# using remote_src +# checks that dest is created +- include: dest_in_non_existent_directories_remote_src.yml + with_items: + - { src: 'foo.txt', dest: 'new_sub_dir1/sub_dir2/', check: 'new_sub_dir1/sub_dir2/foo.txt' } + +# src is a file, dest is file in a non-existent directory: checks that a failure occurs +# using remote_src +- include: src_file_dest_file_in_non_existent_dir_remote_src.yml + with_items: + - 'new_sub_dir1/sub_dir2/foo.txt' + - 'new_sub_dir1/foo.txt' + loop_control: + loop_var: 'dest' + +# src is a file, dest is a non-existent directory (2 levels of directories): +# checks that dest is created +- include: dest_in_non_existent_directories.yml + with_items: + - { src: 'foo.txt', dest: 'new_sub_dir1/sub_dir2/', check: 'new_sub_dir1/sub_dir2/foo.txt' } + - { src: 'subdir', dest: 'new_sub_dir1/sub_dir2/', check: 'new_sub_dir1/sub_dir2/subdir/bar.txt' } + - { src: 'subdir/', dest: 'new_sub_dir1/sub_dir2/', check: 'new_sub_dir1/sub_dir2/bar.txt' } + - { src: 'subdir', dest: 'new_sub_dir1/sub_dir2', check: 'new_sub_dir1/sub_dir2/subdir/bar.txt' } + - { src: 'subdir/', dest: 'new_sub_dir1/sub_dir2', check: 'new_sub_dir1/sub_dir2/bar.txt' } + +# src is a file, dest is file in a non-existent directory: checks that a failure occurs +- include: src_file_dest_file_in_non_existent_dir.yml + with_items: + - 'new_sub_dir1/sub_dir2/foo.txt' + - 'new_sub_dir1/foo.txt' + loop_control: + loop_var: 'dest' +# +# Recursive copying on remote host +# +## prepare for test +- block: + + - name: execute - Create a test src dir + file: + path: '{{ remote_dir }}/remote_dir_src' + state: directory + + - name: gather - Stat the remote_dir_src + stat: + path: '{{ remote_dir }}/remote_dir_src' + register: stat_remote_dir_src_before + + - name: execute - Create a subdir + file: + path: '{{ remote_dir }}/remote_dir_src/subdir' + state: directory + + - name: gather - Stat the remote_dir_src/subdir + stat: + path: '{{ remote_dir }}/remote_dir_src/subdir' + register: stat_remote_dir_src_subdir_before + + - name: execute - Create a file in the top of src + copy: + dest: '{{ remote_dir }}/remote_dir_src/file1' + content: 'hello world 1' + + - name: gather - Stat the remote_dir_src/file1 + stat: + path: '{{ remote_dir }}/remote_dir_src/file1' + register: stat_remote_dir_src_file1_before + + - name: execute - Create a file in the subdir + copy: + dest: '{{ remote_dir }}/remote_dir_src/subdir/file12' + content: 'hello world 12' + + - name: gather - Stat the remote_dir_src/subdir/file12 + stat: + path: '{{ remote_dir }}/remote_dir_src/subdir/file12' + register: stat_remote_dir_src_subdir_file12_before + + - name: execute - Create a link to the file12 + file: + path: '{{ remote_dir }}/remote_dir_src/link_file12' + src: '{{ remote_dir }}/remote_dir_src/subdir/file12' + state: link + + - name: gather - Stat the remote_dir_src/link_file12 + stat: + path: '{{ remote_dir }}/remote_dir_src/link_file12' + register: stat_remote_dir_src_link_file12_before + +### test when src endswith os.sep and dest isdir +- block: + +### local_follow: True + - name: execute - Create a test dest dir + file: + path: '{{ remote_dir }}/testcase1_local_follow_true' + state: directory + + - name: execute - Copy the directory on remote with local_follow True + copy: + remote_src: True + src: '{{ remote_dir }}/remote_dir_src/' + dest: '{{ remote_dir }}/testcase1_local_follow_true' + local_follow: True + register: testcase1 + + - name: gather - Stat the testcase1_local_follow_true + stat: + path: '{{ remote_dir }}/testcase1_local_follow_true' + register: stat_testcase1_local_follow_true + - name: gather - Stat the testcase1_local_follow_true/subdir + stat: + path: '{{ remote_dir }}/testcase1_local_follow_true/subdir' + register: stat_testcase1_local_follow_true_subdir + - name: gather - Stat the testcase1_local_follow_true/file1 + stat: + path: '{{ remote_dir }}/testcase1_local_follow_true/file1' + register: stat_testcase1_local_follow_true_file1 + - name: gather - Stat the testcase1_local_follow_true/subdir/file12 + stat: + path: '{{ remote_dir }}/testcase1_local_follow_true/subdir/file12' + register: stat_testcase1_local_follow_true_subdir_file12 + - name: gather - Stat the testcase1_local_follow_true/link_file12 + stat: + path: '{{ remote_dir }}/testcase1_local_follow_true/link_file12' + register: stat_testcase1_local_follow_true_link_file12 + + - name: assert - remote_dir_src has copied with local_follow True. + assert: + that: + - testcase1 is changed + - "stat_testcase1_local_follow_true.stat.isdir" + - "stat_testcase1_local_follow_true_subdir.stat.isdir" + - "stat_testcase1_local_follow_true_file1.stat.exists" + - "stat_remote_dir_src_file1_before.stat.checksum == stat_testcase1_local_follow_true_file1.stat.checksum" + - "stat_testcase1_local_follow_true_subdir_file12.stat.exists" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_testcase1_local_follow_true_subdir_file12.stat.checksum" + - "stat_testcase1_local_follow_true_link_file12.stat.exists" + - "not stat_testcase1_local_follow_true_link_file12.stat.islnk" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_testcase1_local_follow_true_link_file12.stat.checksum" + +### local_follow: False + - name: execute - Create a test dest dir + file: + path: '{{ remote_dir }}/testcase1_local_follow_false' + state: directory + + - name: execute - Copy the directory on remote with local_follow False + copy: + remote_src: True + src: '{{ remote_dir }}/remote_dir_src/' + dest: '{{ remote_dir }}/testcase1_local_follow_false' + local_follow: False + register: testcase1 + + - name: gather - Stat the testcase1_local_follow_false + stat: + path: '{{ remote_dir }}/testcase1_local_follow_false' + register: stat_testcase1_local_follow_false + - name: gather - Stat the testcase1_local_follow_false/subdir + stat: + path: '{{ remote_dir }}/testcase1_local_follow_false/subdir' + register: stat_testcase1_local_follow_false_subdir + - name: gather - Stat the testcase1_local_follow_false/file1 + stat: + path: '{{ remote_dir }}/testcase1_local_follow_false/file1' + register: stat_testcase1_local_follow_false_file1 + - name: gather - Stat the testcase1_local_follow_false/subdir/file12 + stat: + path: '{{ remote_dir }}/testcase1_local_follow_false/subdir/file12' + register: stat_testcase1_local_follow_false_subdir_file12 + - name: gather - Stat the testcase1_local_follow_false/link_file12 + stat: + path: '{{ remote_dir }}/testcase1_local_follow_false/link_file12' + register: stat_testcase1_local_follow_false_link_file12 + + - name: assert - remote_dir_src has copied with local_follow True. + assert: + that: + - testcase1 is changed + - "stat_testcase1_local_follow_false.stat.isdir" + - "stat_testcase1_local_follow_false_subdir.stat.isdir" + - "stat_testcase1_local_follow_false_file1.stat.exists" + - "stat_remote_dir_src_file1_before.stat.checksum == stat_testcase1_local_follow_false_file1.stat.checksum" + - "stat_testcase1_local_follow_false_subdir_file12.stat.exists" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_testcase1_local_follow_false_subdir_file12.stat.checksum" + - "stat_testcase1_local_follow_false_link_file12.stat.exists" + - "stat_testcase1_local_follow_false_link_file12.stat.islnk" + +## test when src endswith os.sep and dest not exists + +- block: + - name: execute - Copy the directory on remote with local_follow True + copy: + remote_src: True + src: '{{ remote_dir }}/remote_dir_src/' + dest: '{{ remote_dir }}/testcase2_local_follow_true' + local_follow: True + register: testcase2 + + - name: gather - Stat the testcase2_local_follow_true + stat: + path: '{{ remote_dir }}/testcase2_local_follow_true' + register: stat_testcase2_local_follow_true + - name: gather - Stat the testcase2_local_follow_true/subdir + stat: + path: '{{ remote_dir }}/testcase2_local_follow_true/subdir' + register: stat_testcase2_local_follow_true_subdir + - name: gather - Stat the testcase2_local_follow_true/file1 + stat: + path: '{{ remote_dir }}/testcase2_local_follow_true/file1' + register: stat_testcase2_local_follow_true_file1 + - name: gather - Stat the testcase2_local_follow_true/subdir/file12 + stat: + path: '{{ remote_dir }}/testcase2_local_follow_true/subdir/file12' + register: stat_testcase2_local_follow_true_subdir_file12 + - name: gather - Stat the testcase2_local_follow_true/link_file12 + stat: + path: '{{ remote_dir }}/testcase2_local_follow_true/link_file12' + register: stat_testcase2_local_follow_true_link_file12 + + - name: assert - remote_dir_src has copied with local_follow True. + assert: + that: + - testcase2 is changed + - "stat_testcase2_local_follow_true.stat.isdir" + - "stat_testcase2_local_follow_true_subdir.stat.isdir" + - "stat_testcase2_local_follow_true_file1.stat.exists" + - "stat_remote_dir_src_file1_before.stat.checksum == stat_testcase2_local_follow_true_file1.stat.checksum" + - "stat_testcase2_local_follow_true_subdir_file12.stat.exists" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_testcase2_local_follow_true_subdir_file12.stat.checksum" + - "stat_testcase2_local_follow_true_link_file12.stat.exists" + - "not stat_testcase2_local_follow_true_link_file12.stat.islnk" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_testcase2_local_follow_true_link_file12.stat.checksum" + +### local_follow: False + - name: execute - Copy the directory on remote with local_follow False + copy: + remote_src: True + src: '{{ remote_dir }}/remote_dir_src/' + dest: '{{ remote_dir }}/testcase2_local_follow_false' + local_follow: False + register: testcase2 + + - name: execute - Copy the directory on remote with local_follow False + copy: + remote_src: True + src: '{{ remote_dir }}/remote_dir_src/' + dest: '{{ remote_dir }}/testcase2_local_follow_false' + local_follow: False + register: testcase1 + + - name: gather - Stat the testcase2_local_follow_false + stat: + path: '{{ remote_dir }}/testcase2_local_follow_false' + register: stat_testcase2_local_follow_false + - name: gather - Stat the testcase2_local_follow_false/subdir + stat: + path: '{{ remote_dir }}/testcase2_local_follow_false/subdir' + register: stat_testcase2_local_follow_false_subdir + - name: gather - Stat the testcase2_local_follow_false/file1 + stat: + path: '{{ remote_dir }}/testcase2_local_follow_false/file1' + register: stat_testcase2_local_follow_false_file1 + - name: gather - Stat the testcase2_local_follow_false/subdir/file12 + stat: + path: '{{ remote_dir }}/testcase2_local_follow_false/subdir/file12' + register: stat_testcase2_local_follow_false_subdir_file12 + - name: gather - Stat the testcase2_local_follow_false/link_file12 + stat: + path: '{{ remote_dir }}/testcase2_local_follow_false/link_file12' + register: stat_testcase2_local_follow_false_link_file12 + + - name: assert - remote_dir_src has copied with local_follow True. + assert: + that: + - testcase2 is changed + - "stat_testcase2_local_follow_false.stat.isdir" + - "stat_testcase2_local_follow_false_subdir.stat.isdir" + - "stat_testcase2_local_follow_false_file1.stat.exists" + - "stat_remote_dir_src_file1_before.stat.checksum == stat_testcase2_local_follow_false_file1.stat.checksum" + - "stat_testcase2_local_follow_false_subdir_file12.stat.exists" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_testcase2_local_follow_false_subdir_file12.stat.checksum" + - "stat_testcase2_local_follow_false_link_file12.stat.exists" + - "stat_testcase2_local_follow_false_link_file12.stat.islnk" + +## test when src not endswith os.sep and dest isdir +- block: + +### local_follow: True + - name: execute - Create a test dest dir + file: + path: '{{ remote_dir }}/testcase3_local_follow_true' + state: directory + + - name: execute - Copy the directory on remote with local_follow True + copy: + remote_src: True + src: '{{ remote_dir }}/remote_dir_src' + dest: '{{ remote_dir }}/testcase3_local_follow_true' + local_follow: True + register: testcase3 + + - name: gather - Stat the testcase3_local_follow_true + stat: + path: '{{ remote_dir }}/testcase3_local_follow_true/remote_dir_src' + register: stat_testcase3_local_follow_true_remote_dir_src + - name: gather - Stat the testcase3_local_follow_true/remote_dir_src/subdir + stat: + path: '{{ remote_dir }}/testcase3_local_follow_true/remote_dir_src/subdir' + register: stat_testcase3_local_follow_true_remote_dir_src_subdir + - name: gather - Stat the testcase3_local_follow_true/remote_dir_src/file1 + stat: + path: '{{ remote_dir }}/testcase3_local_follow_true/remote_dir_src/file1' + register: stat_testcase3_local_follow_true_remote_dir_src_file1 + - name: gather - Stat the testcase3_local_follow_true/remote_dir_src/subdir/file12 + stat: + path: '{{ remote_dir }}/testcase3_local_follow_true/remote_dir_src/subdir/file12' + register: stat_testcase3_local_follow_true_remote_dir_src_subdir_file12 + - name: gather - Stat the testcase3_local_follow_true/remote_dir_src/link_file12 + stat: + path: '{{ remote_dir }}/testcase3_local_follow_true/remote_dir_src/link_file12' + register: stat_testcase3_local_follow_true_remote_dir_src_link_file12 + + - name: assert - remote_dir_src has copied with local_follow True. + assert: + that: + - testcase3 is changed + - "stat_testcase3_local_follow_true_remote_dir_src.stat.isdir" + - "stat_testcase3_local_follow_true_remote_dir_src_subdir.stat.isdir" + - "stat_testcase3_local_follow_true_remote_dir_src_file1.stat.exists" + - "stat_remote_dir_src_file1_before.stat.checksum == stat_testcase3_local_follow_true_remote_dir_src_file1.stat.checksum" + - "stat_testcase3_local_follow_true_remote_dir_src_subdir_file12.stat.exists" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_testcase3_local_follow_true_remote_dir_src_subdir_file12.stat.checksum" + - "stat_testcase3_local_follow_true_remote_dir_src_link_file12.stat.exists" + - "not stat_testcase3_local_follow_true_remote_dir_src_link_file12.stat.islnk" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_testcase3_local_follow_true_remote_dir_src_link_file12.stat.checksum" + +### local_follow: False + - name: execute - Create a test dest dir + file: + path: '{{ remote_dir }}/testcase3_local_follow_false' + state: directory + + - name: execute - Copy the directory on remote with local_follow False + copy: + remote_src: True + src: '{{ remote_dir }}/remote_dir_src' + dest: '{{ remote_dir }}/testcase3_local_follow_false' + local_follow: False + register: testcase3 + + - name: gather - Stat the testcase3_local_follow_false + stat: + path: '{{ remote_dir }}/testcase3_local_follow_false/remote_dir_src' + register: stat_testcase3_local_follow_false_remote_dir_src + - name: gather - Stat the testcase3_local_follow_false/remote_dir_src/subdir + stat: + path: '{{ remote_dir }}/testcase3_local_follow_false/remote_dir_src/subdir' + register: stat_testcase3_local_follow_false_remote_dir_src_subdir + - name: gather - Stat the testcase3_local_follow_false/remote_dir_src/file1 + stat: + path: '{{ remote_dir }}/testcase3_local_follow_false/remote_dir_src/file1' + register: stat_testcase3_local_follow_false_remote_dir_src_file1 + - name: gather - Stat the testcase3_local_follow_false/remote_dir_src/subdir/file12 + stat: + path: '{{ remote_dir }}/testcase3_local_follow_false/remote_dir_src/subdir/file12' + register: stat_testcase3_local_follow_false_remote_dir_src_subdir_file12 + - name: gather - Stat the testcase3_local_follow_false/remote_dir_src/link_file12 + stat: + path: '{{ remote_dir }}/testcase3_local_follow_false/remote_dir_src/link_file12' + register: stat_testcase3_local_follow_false_remote_dir_src_link_file12 + + - name: assert - remote_dir_src has copied with local_follow False. + assert: + that: + - testcase3 is changed + - "stat_testcase3_local_follow_false_remote_dir_src.stat.isdir" + - "stat_testcase3_local_follow_false_remote_dir_src_subdir.stat.isdir" + - "stat_testcase3_local_follow_false_remote_dir_src_file1.stat.exists" + - "stat_remote_dir_src_file1_before.stat.checksum == stat_testcase3_local_follow_false_remote_dir_src_file1.stat.checksum" + - "stat_testcase3_local_follow_false_remote_dir_src_subdir_file12.stat.exists" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_testcase3_local_follow_false_remote_dir_src_subdir_file12.stat.checksum" + - "stat_testcase3_local_follow_false_remote_dir_src_link_file12.stat.exists" + - "stat_testcase3_local_follow_false_remote_dir_src_link_file12.stat.islnk" + +## test when src not endswith os.sep and dest not exists +- block: +### local_follow: True + - name: execute - Copy the directory on remote with local_follow True + copy: + remote_src: True + src: '{{ remote_dir }}/remote_dir_src' + dest: '{{ remote_dir }}/testcase4_local_follow_true' + local_follow: True + register: testcase4 + + - name: gather - Stat the testcase4_local_follow_true + stat: + path: '{{ remote_dir }}/testcase4_local_follow_true/remote_dir_src' + register: stat_testcase4_local_follow_true_remote_dir_src + - name: gather - Stat the testcase4_local_follow_true/remote_dir_src/subdir + stat: + path: '{{ remote_dir }}/testcase4_local_follow_true/remote_dir_src/subdir' + register: stat_testcase4_local_follow_true_remote_dir_src_subdir + - name: gather - Stat the testcase4_local_follow_true/remote_dir_src/file1 + stat: + path: '{{ remote_dir }}/testcase4_local_follow_true/remote_dir_src/file1' + register: stat_testcase4_local_follow_true_remote_dir_src_file1 + - name: gather - Stat the testcase4_local_follow_true/remote_dir_src/subdir/file12 + stat: + path: '{{ remote_dir }}/testcase4_local_follow_true/remote_dir_src/subdir/file12' + register: stat_testcase4_local_follow_true_remote_dir_src_subdir_file12 + - name: gather - Stat the testcase4_local_follow_true/remote_dir_src/link_file12 + stat: + path: '{{ remote_dir }}/testcase4_local_follow_true/remote_dir_src/link_file12' + register: stat_testcase4_local_follow_true_remote_dir_src_link_file12 + + - name: assert - remote_dir_src has copied with local_follow True. + assert: + that: + - testcase4 is changed + - "stat_testcase4_local_follow_true_remote_dir_src.stat.isdir" + - "stat_testcase4_local_follow_true_remote_dir_src_subdir.stat.isdir" + - "stat_testcase4_local_follow_true_remote_dir_src_file1.stat.exists" + - "stat_remote_dir_src_file1_before.stat.checksum == stat_testcase4_local_follow_true_remote_dir_src_file1.stat.checksum" + - "stat_testcase4_local_follow_true_remote_dir_src_subdir_file12.stat.exists" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_testcase4_local_follow_true_remote_dir_src_subdir_file12.stat.checksum" + - "stat_testcase4_local_follow_true_remote_dir_src_link_file12.stat.exists" + - "not stat_testcase4_local_follow_true_remote_dir_src_link_file12.stat.islnk" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_testcase4_local_follow_true_remote_dir_src_link_file12.stat.checksum" + +### local_follow: False + - name: execute - Copy the directory on remote with local_follow False + copy: + remote_src: True + src: '{{ remote_dir }}/remote_dir_src' + dest: '{{ remote_dir }}/testcase4_local_follow_false' + local_follow: False + register: testcase4 + + - name: gather - Stat the testcase4_local_follow_false + stat: + path: '{{ remote_dir }}/testcase4_local_follow_false/remote_dir_src' + register: stat_testcase4_local_follow_false_remote_dir_src + - name: gather - Stat the testcase4_local_follow_false/remote_dir_src/subdir + stat: + path: '{{ remote_dir }}/testcase4_local_follow_false/remote_dir_src/subdir' + register: stat_testcase4_local_follow_false_remote_dir_src_subdir + - name: gather - Stat the testcase4_local_follow_false/remote_dir_src/file1 + stat: + path: '{{ remote_dir }}/testcase4_local_follow_false/remote_dir_src/file1' + register: stat_testcase4_local_follow_false_remote_dir_src_file1 + - name: gather - Stat the testcase4_local_follow_false/remote_dir_src/subdir/file12 + stat: + path: '{{ remote_dir }}/testcase4_local_follow_false/remote_dir_src/subdir/file12' + register: stat_testcase4_local_follow_false_remote_dir_src_subdir_file12 + - name: gather - Stat the testcase4_local_follow_false/remote_dir_src/link_file12 + stat: + path: '{{ remote_dir }}/testcase4_local_follow_false/remote_dir_src/link_file12' + register: stat_testcase4_local_follow_false_remote_dir_src_link_file12 + + - name: assert - remote_dir_src has copied with local_follow False. + assert: + that: + - testcase4 is changed + - "stat_testcase4_local_follow_false_remote_dir_src.stat.isdir" + - "stat_testcase4_local_follow_false_remote_dir_src_subdir.stat.isdir" + - "stat_testcase4_local_follow_false_remote_dir_src_file1.stat.exists" + - "stat_remote_dir_src_file1_before.stat.checksum == stat_testcase4_local_follow_false_remote_dir_src_file1.stat.checksum" + - "stat_testcase4_local_follow_false_remote_dir_src_subdir_file12.stat.exists" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_testcase4_local_follow_false_remote_dir_src_subdir_file12.stat.checksum" + - "stat_testcase4_local_follow_false_remote_dir_src_link_file12.stat.exists" + - "stat_testcase4_local_follow_false_remote_dir_src_link_file12.stat.islnk" + +- block: + - name: execute - Clone the source directory on remote + copy: + remote_src: True + src: '{{ remote_dir }}/remote_dir_src/' + dest: '{{ remote_dir }}/testcase5_remote_src_subdirs_src' + - name: Create a 2nd level subdirectory + file: + path: '{{ remote_dir }}/testcase5_remote_src_subdirs_src/subdir/subdir2/' + state: directory + - name: execute - Copy the directory on remote + copy: + remote_src: True + src: '{{ remote_dir }}/testcase5_remote_src_subdirs_src/' + dest: '{{ remote_dir }}/testcase5_remote_src_subdirs_dest' + local_follow: True + - name: execute - Create a new file in the subdir + copy: + dest: '{{ remote_dir }}/testcase5_remote_src_subdirs_src/subdir/subdir2/file13' + content: 'very new file' + - name: gather - Stat the testcase5_remote_src_subdirs_src/subdir/subdir2/file13 + stat: + path: '{{ remote_dir }}/testcase5_remote_src_subdirs_src/subdir/subdir2/file13' + - name: execute - Copy the directory on remote + copy: + remote_src: True + src: '{{ remote_dir }}/testcase5_remote_src_subdirs_src/' + dest: '{{ remote_dir }}/testcase5_remote_src_subdirs_dest/' + register: testcase5_new + - name: execute - Edit a file in the subdir + copy: + dest: '{{ remote_dir }}/testcase5_remote_src_subdirs_src/subdir/subdir2/file13' + content: 'NOT hello world 12' + - name: gather - Stat the testcase5_remote_src_subdirs_src/subdir/subdir2/file13 + stat: + path: '{{ remote_dir }}/testcase5_remote_src_subdirs_src/subdir/subdir2/file13' + register: stat_testcase5_remote_src_subdirs_file13_before + - name: execute - Copy the directory on remote + copy: + remote_src: True + src: '{{ remote_dir }}/testcase5_remote_src_subdirs_src/' + dest: '{{ remote_dir }}/testcase5_remote_src_subdirs_dest/' + register: testcase5_edited + - name: gather - Stat the testcase5_remote_src_subdirs_dest/subdir/subdir2/file13 + stat: + path: '{{ remote_dir }}/testcase5_remote_src_subdirs_dest/subdir/subdir2/file13' + register: stat_testcase5_remote_src_subdirs_file13 + - name: assert - remote_dir_src has copied with local_follow False. + assert: + that: + - testcase5_new is changed + - testcase5_edited is changed + - "stat_testcase5_remote_src_subdirs_file13.stat.exists" + - "stat_testcase5_remote_src_subdirs_file13_before.stat.checksum == stat_testcase5_remote_src_subdirs_file13.stat.checksum" + + +## test copying the directory on remote with chown + + +- block: + + - set_fact: + ansible_copy_test_user_name: 'ansible_copy_test_{{ 100000 | random }}' + + - name: execute - create a user for test + user: + name: '{{ ansible_copy_test_user_name }}' + state: present + become: true + register: ansible_copy_test_user + + - name: execute - create a group for test + group: + name: '{{ ansible_copy_test_user_name }}' + state: present + become: true + register: ansible_copy_test_group + + - name: execute - Copy the directory on remote with chown + copy: + remote_src: True + src: '{{ remote_dir_expanded }}/remote_dir_src/' + dest: '{{ remote_dir_expanded }}/new_dir_with_chown' + owner: '{{ ansible_copy_test_user_name }}' + group: '{{ ansible_copy_test_user_name }}' + follow: true + register: testcase5 + become: true + + - name: gather - Stat the new_dir_with_chown + stat: + path: '{{ remote_dir }}/new_dir_with_chown' + register: stat_new_dir_with_chown + + - name: gather - Stat the new_dir_with_chown/file1 + stat: + path: '{{ remote_dir }}/new_dir_with_chown/file1' + register: stat_new_dir_with_chown_file1 + + - name: gather - Stat the new_dir_with_chown/subdir + stat: + path: '{{ remote_dir }}/new_dir_with_chown/subdir' + register: stat_new_dir_with_chown_subdir + + - name: gather - Stat the new_dir_with_chown/subdir/file12 + stat: + path: '{{ remote_dir }}/new_dir_with_chown/subdir/file12' + register: stat_new_dir_with_chown_subdir_file12 + + - name: gather - Stat the new_dir_with_chown/link_file12 + stat: + path: '{{ remote_dir }}/new_dir_with_chown/link_file12' + register: stat_new_dir_with_chown_link_file12 + + - name: assert - owner and group have changed + assert: + that: + - testcase5 is changed + - "stat_new_dir_with_chown.stat.uid == {{ ansible_copy_test_user.uid }}" + - "stat_new_dir_with_chown.stat.gid == {{ ansible_copy_test_group.gid }}" + - "stat_new_dir_with_chown.stat.pw_name == '{{ ansible_copy_test_user_name }}'" + - "stat_new_dir_with_chown.stat.gr_name == '{{ ansible_copy_test_user_name }}'" + - "stat_new_dir_with_chown_file1.stat.uid == {{ ansible_copy_test_user.uid }}" + - "stat_new_dir_with_chown_file1.stat.gid == {{ ansible_copy_test_group.gid }}" + - "stat_new_dir_with_chown_file1.stat.pw_name == '{{ ansible_copy_test_user_name }}'" + - "stat_new_dir_with_chown_file1.stat.gr_name == '{{ ansible_copy_test_user_name }}'" + - "stat_new_dir_with_chown_subdir.stat.uid == {{ ansible_copy_test_user.uid }}" + - "stat_new_dir_with_chown_subdir.stat.gid == {{ ansible_copy_test_group.gid }}" + - "stat_new_dir_with_chown_subdir.stat.pw_name == '{{ ansible_copy_test_user_name }}'" + - "stat_new_dir_with_chown_subdir.stat.gr_name == '{{ ansible_copy_test_user_name }}'" + - "stat_new_dir_with_chown_subdir_file12.stat.uid == {{ ansible_copy_test_user.uid }}" + - "stat_new_dir_with_chown_subdir_file12.stat.gid == {{ ansible_copy_test_group.gid }}" + - "stat_new_dir_with_chown_subdir_file12.stat.pw_name == '{{ ansible_copy_test_user_name }}'" + - "stat_new_dir_with_chown_subdir_file12.stat.gr_name == '{{ ansible_copy_test_user_name }}'" + - "stat_new_dir_with_chown_link_file12.stat.uid == {{ ansible_copy_test_user.uid }}" + - "stat_new_dir_with_chown_link_file12.stat.gid == {{ ansible_copy_test_group.gid }}" + - "stat_new_dir_with_chown_link_file12.stat.pw_name == '{{ ansible_copy_test_user_name }}'" + - "stat_new_dir_with_chown_link_file12.stat.gr_name == '{{ ansible_copy_test_user_name }}'" + + always: + - name: execute - remove the user for test + user: + name: '{{ ansible_copy_test_user_name }}' + state: absent + remove: yes + become: true + + - name: execute - remove the group for test + group: + name: '{{ ansible_copy_test_user_name }}' + state: absent + become: true + +## testcase last - make sure remote_dir_src not change +- block: + - name: Stat the remote_dir_src + stat: + path: '{{ remote_dir }}/remote_dir_src' + register: stat_remote_dir_src_after + + - name: Stat the remote_dir_src/subdir + stat: + path: '{{ remote_dir }}/remote_dir_src/subdir' + register: stat_remote_dir_src_subdir_after + + - name: Stat the remote_dir_src/file1 + stat: + path: '{{ remote_dir }}/remote_dir_src/file1' + register: stat_remote_dir_src_file1_after + + - name: Stat the remote_dir_src/subdir/file12 + stat: + path: '{{ remote_dir }}/remote_dir_src/subdir/file12' + register: stat_remote_dir_src_subdir_file12_after + + - name: Stat the remote_dir_src/link_file12 + stat: + path: '{{ remote_dir }}/remote_dir_src/link_file12' + register: stat_remote_dir_src_link_file12_after + + - name: Assert that remote_dir_src not change. + assert: + that: + - "stat_remote_dir_src_after.stat.exists" + - "stat_remote_dir_src_after.stat.isdir" + - "stat_remote_dir_src_before.stat.uid == stat_remote_dir_src_after.stat.uid" + - "stat_remote_dir_src_before.stat.gid == stat_remote_dir_src_after.stat.gid" + - "stat_remote_dir_src_before.stat.pw_name == stat_remote_dir_src_after.stat.pw_name" + - "stat_remote_dir_src_before.stat.gr_name == stat_remote_dir_src_after.stat.gr_name" + - "stat_remote_dir_src_before.stat.path == stat_remote_dir_src_after.stat.path" + - "stat_remote_dir_src_before.stat.mode == stat_remote_dir_src_after.stat.mode" + + - "stat_remote_dir_src_subdir_after.stat.exists" + - "stat_remote_dir_src_subdir_after.stat.isdir" + - "stat_remote_dir_src_subdir_before.stat.uid == stat_remote_dir_src_subdir_after.stat.uid" + - "stat_remote_dir_src_subdir_before.stat.gid == stat_remote_dir_src_subdir_after.stat.gid" + - "stat_remote_dir_src_subdir_before.stat.pw_name == stat_remote_dir_src_subdir_after.stat.pw_name" + - "stat_remote_dir_src_subdir_before.stat.gr_name == stat_remote_dir_src_subdir_after.stat.gr_name" + - "stat_remote_dir_src_subdir_before.stat.path == stat_remote_dir_src_subdir_after.stat.path" + - "stat_remote_dir_src_subdir_before.stat.mode == stat_remote_dir_src_subdir_after.stat.mode" + + - "stat_remote_dir_src_file1_after.stat.exists" + - "stat_remote_dir_src_file1_before.stat.uid == stat_remote_dir_src_file1_after.stat.uid" + - "stat_remote_dir_src_file1_before.stat.gid == stat_remote_dir_src_file1_after.stat.gid" + - "stat_remote_dir_src_file1_before.stat.pw_name == stat_remote_dir_src_file1_after.stat.pw_name" + - "stat_remote_dir_src_file1_before.stat.gr_name == stat_remote_dir_src_file1_after.stat.gr_name" + - "stat_remote_dir_src_file1_before.stat.path == stat_remote_dir_src_file1_after.stat.path" + - "stat_remote_dir_src_file1_before.stat.mode == stat_remote_dir_src_file1_after.stat.mode" + - "stat_remote_dir_src_file1_before.stat.checksum == stat_remote_dir_src_file1_after.stat.checksum" + + - "stat_remote_dir_src_subdir_file12_after.stat.exists" + - "stat_remote_dir_src_subdir_file12_before.stat.uid == stat_remote_dir_src_subdir_file12_after.stat.uid" + - "stat_remote_dir_src_subdir_file12_before.stat.gid == stat_remote_dir_src_subdir_file12_after.stat.gid" + - "stat_remote_dir_src_subdir_file12_before.stat.pw_name == stat_remote_dir_src_subdir_file12_after.stat.pw_name" + - "stat_remote_dir_src_subdir_file12_before.stat.gr_name == stat_remote_dir_src_subdir_file12_after.stat.gr_name" + - "stat_remote_dir_src_subdir_file12_before.stat.path == stat_remote_dir_src_subdir_file12_after.stat.path" + - "stat_remote_dir_src_subdir_file12_before.stat.mode == stat_remote_dir_src_subdir_file12_after.stat.mode" + - "stat_remote_dir_src_subdir_file12_before.stat.checksum == stat_remote_dir_src_subdir_file12_after.stat.checksum" + + - "stat_remote_dir_src_link_file12_after.stat.exists" + - "stat_remote_dir_src_link_file12_after.stat.islnk" + - "stat_remote_dir_src_link_file12_before.stat.uid == stat_remote_dir_src_link_file12_after.stat.uid" + - "stat_remote_dir_src_link_file12_before.stat.gid == stat_remote_dir_src_link_file12_after.stat.gid" + - "stat_remote_dir_src_link_file12_before.stat.pw_name == stat_remote_dir_src_link_file12_after.stat.pw_name" + - "stat_remote_dir_src_link_file12_before.stat.gr_name == stat_remote_dir_src_link_file12_after.stat.gr_name" + - "stat_remote_dir_src_link_file12_before.stat.path == stat_remote_dir_src_link_file12_after.stat.path" + - "stat_remote_dir_src_link_file12_before.stat.mode == stat_remote_dir_src_link_file12_after.stat.mode" + +# Test for issue 69783: copy with remote_src=yes and src='dir/' preserves all permissions +- block: + - name: Create directory structure + file: + path: "{{ local_temp_dir }}/test69783/{{ item }}" + state: directory + loop: + - "src/dir" + - "dest" + + - name: Create source file structure + file: + path: "{{ local_temp_dir }}/test69783/src/{{ item.name }}" + state: touch + mode: "{{ item.mode }}" + loop: + - { name: 'readwrite', mode: '0644' } + - { name: 'executable', mode: '0755' } + - { name: 'readonly', mode: '0444' } + - { name: 'dir/readwrite', mode: '0644' } + - { name: 'dir/executable', mode: '0755' } + - { name: 'dir/readonly', mode: '0444' } + + - name: Recursive remote copy with preserve + copy: + src: "{{ local_temp_dir }}/test69783/src/" + dest: "{{ local_temp_dir }}/test69783/dest/" + remote_src: yes + mode: preserve + + - name: Stat dest 'readwrite' file + stat: + path: "{{ local_temp_dir}}/test69783/dest/readwrite" + register: dest_readwrite_stat + + - name: Stat dest 'executable' file + stat: + path: "{{ local_temp_dir}}/test69783/dest/executable" + register: dest_executable_stat + + - name: Stat dest 'readonly' file + stat: + path: "{{ local_temp_dir}}/test69783/dest/readonly" + register: dest_readonly_stat + + - name: Stat dest 'dir/readwrite' file + stat: + path: "{{ local_temp_dir}}/test69783/dest/dir/readwrite" + register: dest_dir_readwrite_stat + + - name: Stat dest 'dir/executable' file + stat: + path: "{{ local_temp_dir}}/test69783/dest/dir/executable" + register: dest_dir_executable_stat + + - name: Stat dest 'dir/readonly' file + stat: + path: "{{ local_temp_dir}}/test69783/dest/dir/readonly" + register: dest_dir_readonly_stat + + - name: Assert modes are preserved + assert: + that: + - "dest_readwrite_stat.stat.mode == '0644'" + - "dest_executable_stat.stat.mode == '0755'" + - "dest_readonly_stat.stat.mode == '0444'" + - "dest_dir_readwrite_stat.stat.mode == '0644'" + - "dest_dir_executable_stat.stat.mode == '0755'" + - "dest_dir_readonly_stat.stat.mode == '0444'" |