diff options
-rw-r--r-- | changelogs/fragments/dont_move_non_files.yml | 2 | ||||
-rw-r--r-- | lib/ansible/modules/copy.py | 31 | ||||
-rw-r--r-- | test/integration/targets/copy/tasks/main.yml | 3 | ||||
-rw-r--r-- | test/integration/targets/copy/tasks/src_remote_file_is_not_file.yml | 39 |
4 files changed, 62 insertions, 13 deletions
diff --git a/changelogs/fragments/dont_move_non_files.yml b/changelogs/fragments/dont_move_non_files.yml new file mode 100644 index 0000000000..1f6a1e7986 --- /dev/null +++ b/changelogs/fragments/dont_move_non_files.yml @@ -0,0 +1,2 @@ +bugfixes: + - copy module will no longer move 'non files' set as src when remote_src=true. diff --git a/lib/ansible/modules/copy.py b/lib/ansible/modules/copy.py index ca6ae0df0e..09ec4a7d79 100644 --- a/lib/ansible/modules/copy.py +++ b/lib/ansible/modules/copy.py @@ -576,23 +576,24 @@ def main(): module.params['mode'] = '0%03o' % stat.S_IMODE(os.stat(b_src).st_mode) mode = module.params['mode'] + changed = False + checksum_dest = None + checksum_src = None + md5sum_src = None if os.path.isfile(src): - checksum_src = module.sha1(src) - else: - checksum_src = None - - # Backwards compat only. This will be None in FIPS mode - try: - if os.path.isfile(src): + try: + checksum_src = module.sha1(src) + except (OSError, IOError) as e: + module.warn("Unable to calculate src checksum, assuming change: %s" % to_native(e)) + try: + # Backwards compat only. This will be None in FIPS mode md5sum_src = module.md5(src) - else: - md5sum_src = None - except ValueError: - md5sum_src = None - - changed = False + except ValueError: + pass + elif remote_src and not os.path.isdir(src): + module.fail_json("Cannot copy invalid source '%s': not a file" % to_native(src)) if checksum and checksum_src != checksum: module.fail_json( @@ -657,6 +658,7 @@ def main(): backup_file = None if checksum_src != checksum_dest or os.path.islink(b_dest): + if not module.check_mode: try: if backup: @@ -680,8 +682,10 @@ def main(): (rc, out, err) = module.run_command(validate % src) if rc != 0: module.fail_json(msg="failed to validate", exit_status=rc, stdout=out, stderr=err) + b_mysrc = b_src if remote_src and os.path.isfile(b_src): + _, b_mysrc = tempfile.mkstemp(dir=os.path.dirname(b_dest)) shutil.copyfile(b_src, b_mysrc) @@ -701,6 +705,7 @@ def main(): # assume unwanted ACLs by default src_has_acls = True + # at this point we should always have tmp file module.atomic_move(b_mysrc, dest, unsafe_writes=module.params['unsafe_writes']) if PY3 and hasattr(os, 'listxattr') and platform.system() == 'Linux' and not remote_src: diff --git a/test/integration/targets/copy/tasks/main.yml b/test/integration/targets/copy/tasks/main.yml index a5211de1bd..7538e72fd5 100644 --- a/test/integration/targets/copy/tasks/main.yml +++ b/test/integration/targets/copy/tasks/main.yml @@ -97,6 +97,9 @@ - 'diff_output.diff[0].before == ""' - '"Ansible managed" in diff_output.diff[0].after' + - name: tests with remote_src and non files + import_tasks: src_remote_file_is_not_file.yml + always: - name: Cleaning file: diff --git a/test/integration/targets/copy/tasks/src_remote_file_is_not_file.yml b/test/integration/targets/copy/tasks/src_remote_file_is_not_file.yml new file mode 100644 index 0000000000..2cda7d37c9 --- /dev/null +++ b/test/integration/targets/copy/tasks/src_remote_file_is_not_file.yml @@ -0,0 +1,39 @@ +- name: test remote src non files + vars: + destfile: '{{remote_dir}}/whocares' + block: + - name: mess with dev/null + copy: + src: /dev/null + dest: "{{destfile}}" + remote_src: true + become: true + register: dev_null_fail + ignore_errors: true + + - name: ensure we failed + assert: + that: + - dev_null_fail is failed + - "'not a file' in dev_null_fail.msg" + + - name: now with file existing + file: state=touch path="{{destfile}}" + + - name: mess with dev/null again + copy: + src: /dev/null + dest: "{{destfile}}" + remote_src: true + become: true + register: dev_null_fail + ignore_errors: true + + - name: ensure we failed, again + assert: + that: + - dev_null_fail is failed + - "'not a file' in dev_null_fail.msg" + always: + - name: cleanup + file: state=absent path="{{destfile}}" |