diff --git a/lib/ansible/plugins/action/copy.py b/lib/ansible/plugins/action/copy.py index 412ef54bd9..55aba4dc17 100644 --- a/lib/ansible/plugins/action/copy.py +++ b/lib/ansible/plugins/action/copy.py @@ -442,8 +442,14 @@ class ActionModule(ActionBase): elif remote_src: result.update(self._execute_module(task_vars=task_vars)) return result - else: # find in expected paths + else: + # find_needle returns a path that may not have a trailing slash on + # a directory so we need to determine that now (we use it just + # like rsync does to figure out whether to include the directory + # or only the files inside the directory + trailing_slash = source.endswith(os.path.sep) try: + # find in expected paths source = self._find_needle('files', source) except AnsibleError as e: result['failed'] = True @@ -451,6 +457,12 @@ class ActionModule(ActionBase): result['exception'] = traceback.format_exc() return result + if trailing_slash != source.endswith(os.path.sep): + if source[-1] == os.path.sep: + source = source[:-1] + else: + source = source + os.path.sep + # A list of source file tuples (full_path, relative_path) which will try to copy to the destination source_files = {'files': [], 'directories': [], 'symlinks': []} diff --git a/test/integration/targets/copy/tasks/main.yml b/test/integration/targets/copy/tasks/main.yml index 9041ff3a26..dd8fb3e8c3 100644 --- a/test/integration/targets/copy/tasks/main.yml +++ b/test/integration/targets/copy/tasks/main.yml @@ -5,6 +5,9 @@ # GNU General Public License v3 or later (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt ) # +- set_fact: + output_dir_expanded: '{{ output_dir | expanduser }}' + - name: record the output directory set_fact: output_file={{output_dir}}/foo.txt @@ -650,6 +653,83 @@ that: - 'not copy_result.changed' +# +# Recursive copy with absolute paths (#27439) +# +- name: Test that output_dir is appropriate for this test (absolute path) + assert: + that: + - '{{ output_dir_expanded[0] == "/" }}' + +- name: create a directory to copy + file: + path: '{{ output_dir_expanded }}/source_recursive' + state: directory + +- name: create a file inside of the directory + copy: + content: "testing" + dest: '{{ output_dir_expanded }}/source_recursive/file' + +- name: Create a directory to place the test output in + file: + path: '{{ output_dir_expanded }}/destination' + state: directory + +- name: Copy the directory and files within (no trailing slash) + copy: + src: '{{ output_dir_expanded }}/source_recursive' + dest: '{{ output_dir_expanded }}/destination' + +- name: stat the recursively copied directory + stat: path={{output_dir}}/destination/{{item}} + register: copied_stat + with_items: + - "source_recursive" + - "source_recursive/file" + - "file" + +#- debug: var=copied_stat +- 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: '{{ output_dir_expanded }}/destination' + state: absent + +# Try again with no trailing slash + +- name: Create a directory to place the test output in + file: + path: '{{ output_dir_expanded }}/destination' + state: directory + +- name: Copy just the files inside of the directory + copy: + src: '{{ output_dir_expanded }}/source_recursive/' + dest: '{{ output_dir_expanded }}/destination' + +- name: stat the recursively copied directory + stat: path={{output_dir_expanded}}/destination/{{item}} + register: copied_stat + with_items: + - "source_recursive" + - "source_recursive/file" + - "file" + +#- debug: var=copied_stat +- 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" + # # issue 8394 # @@ -671,7 +751,7 @@ assert: that: - "copy_result6.changed" - - "copy_result6.dest == '{{output_dir|expanduser}}/multiline.txt'" + - "copy_result6.dest == '{{output_dir_expanded}}/multiline.txt'" - "copy_result6.checksum == '9cd0697c6a9ff6689f0afb9136fa62e0b3fee903'" # test overwriting a file as an unprivileged user (pull request #8624)