diff --git a/lib/ansible/plugins/action/win_copy.py b/lib/ansible/plugins/action/win_copy.py index d299c4471a..71601fdfd7 100644 --- a/lib/ansible/plugins/action/win_copy.py +++ b/lib/ansible/plugins/action/win_copy.py @@ -15,14 +15,14 @@ import tempfile import traceback import zipfile -from ansible.errors import AnsibleError +from ansible.errors import AnsibleError, AnsibleFileNotFound from ansible.module_utils._text import to_bytes, to_native, to_text from ansible.module_utils.parsing.convert_bool import boolean from ansible.plugins.action import ActionBase from ansible.utils.hashing import checksum -def _walk_dirs(topdir, base_path=None, local_follow=False, trailing_slash_detector=None, checksum_check=False): +def _walk_dirs(topdir, loader, base_path=None, local_follow=False, trailing_slash_detector=None, checksum_check=False): """ Walk a filesystem tree returning enough information to copy the files. This is similar to the _walk_dirs function in ``copy.py`` but returns @@ -30,6 +30,7 @@ def _walk_dirs(topdir, base_path=None, local_follow=False, trailing_slash_detect a local file if wanted. :arg topdir: The directory that the filesystem tree is rooted at + :arg loader: The self._loader object from ActionBase :kwarg base_path: The initial directory structure to strip off of the files for the destination directory. If this is None (the default), the base_path is set to ``top_dir``. @@ -100,7 +101,7 @@ def _walk_dirs(topdir, base_path=None, local_follow=False, trailing_slash_detect if os.path.islink(filepath): # Dereference the symlnk - real_file = os.path.realpath(filepath) + real_file = loader.get_real_file(os.path.realpath(filepath), decrypt=True) if local_follow and os.path.isfile(real_file): # Add the file pointed to by the symlink r_files['files'].append( @@ -115,11 +116,12 @@ def _walk_dirs(topdir, base_path=None, local_follow=False, trailing_slash_detect r_files['symlinks'].append({"src": os.readlink(filepath), "dest": dest_filepath}) else: # Just a normal file + real_file = loader.get_real_file(filepath, decrypt=True) r_files['files'].append( { - "src": filepath, + "src": real_file, "dest": dest_filepath, - "checksum": _get_local_checksum(checksum_check, filepath) + "checksum": _get_local_checksum(checksum_check, real_file) } ) @@ -336,7 +338,7 @@ class ActionModule(ActionBase): content = self._task.args.get('content', None) dest = self._task.args.get('dest', None) remote_src = boolean(self._task.args.get('remote_src', False), strict=False) - follow = boolean(self._task.args.get('follow', False), strict=False) + local_follow = boolean(self._task.args.get('local_follow', False), strict=False) force = boolean(self._task.args.get('force', True), strict=False) result['src'] = source @@ -412,7 +414,7 @@ class ActionModule(ActionBase): result['operation'] = 'folder_copy' # Get a list of the files we want to replicate on the remote side - source_files = _walk_dirs(source, local_follow=follow, + source_files = _walk_dirs(source, self._loader, local_follow=local_follow, trailing_slash_detector=self._connection._shell.path_has_trailing_slash, checksum_check=force) @@ -426,6 +428,14 @@ class ActionModule(ActionBase): else: result['operation'] = 'file_copy' + # If the local file does not exist, get_real_file() raises AnsibleFileNotFound + try: + source_full = self._loader.get_real_file(source, decrypt=True) + except AnsibleFileNotFound as e: + result['failed'] = True + result['msg'] = "could not find src=%s, %s" % (source_full, to_text(e)) + return result + original_basename = os.path.basename(source) result['original_basename'] = original_basename @@ -440,16 +450,16 @@ class ActionModule(ActionBase): filename = os.path.basename(unix_path) check_dest = os.path.dirname(unix_path) - file_checksum = _get_local_checksum(force, source) + file_checksum = _get_local_checksum(force, source_full) source_files['files'].append( dict( - src=source, + src=source_full, dest=filename, checksum=file_checksum ) ) result['checksum'] = file_checksum - result['size'] = os.path.getsize(to_bytes(source, errors='surrogate_or_strict')) + result['size'] = os.path.getsize(to_bytes(source_full, errors='surrogate_or_strict')) # find out the files/directories/symlinks that we need to copy to the server query_args = self._task.args.copy() diff --git a/test/integration/targets/win_copy/files-vault/folder/nested-vault-file b/test/integration/targets/win_copy/files-vault/folder/nested-vault-file new file mode 100644 index 0000000000..d8d1549874 --- /dev/null +++ b/test/integration/targets/win_copy/files-vault/folder/nested-vault-file @@ -0,0 +1,6 @@ +$ANSIBLE_VAULT;1.1;AES256 +65653164323866373138353632323531393664393563633665373635623763353561386431373366 +3232353263363034313136663062623336663463373966320a333763323032646463386432626161 +36386330356637666362396661653935653064623038333031653335626164376465353235303636 +3335616231663838620a303632343938326538656233393562303162343261383465623261646664 +33613932343461626339333832363930303962633364303736376634396364643861 diff --git a/test/integration/targets/win_copy/files-vault/readme.txt b/test/integration/targets/win_copy/files-vault/readme.txt new file mode 100644 index 0000000000..dae883b5ee --- /dev/null +++ b/test/integration/targets/win_copy/files-vault/readme.txt @@ -0,0 +1,5 @@ +This directory contains some files that have been encrypted with ansible-vault. + +This is to test out the decrypt parameter in win_copy. + +The password is: password diff --git a/test/integration/targets/win_copy/files-vault/vault-file b/test/integration/targets/win_copy/files-vault/vault-file new file mode 100644 index 0000000000..2fff7619a7 --- /dev/null +++ b/test/integration/targets/win_copy/files-vault/vault-file @@ -0,0 +1,6 @@ +$ANSIBLE_VAULT;1.1;AES256 +30353665333635633433356261616636356130386330363962386533303566313463383734373532 +3933643234323638623939613462346361313431363939370a303532656338353035346661353965 +34656231633238396361393131623834316262306533663838336362366137306562646561383766 +6363373965633337640a373666336461613337346131353564383134326139616561393664663563 +3431 diff --git a/test/integration/targets/win_copy/tasks/tests.yml b/test/integration/targets/win_copy/tasks/tests.yml index b5f3d8dc30..04e2ca5829 100644 --- a/test/integration/targets/win_copy/tasks/tests.yml +++ b/test/integration/targets/win_copy/tasks/tests.yml @@ -26,6 +26,32 @@ register: fail_missing_parent_dir failed_when: "'Destination directory ' + test_win_copy_path + '\\missing-dir does not exist' not in fail_missing_parent_dir.msg" +- name: fail to copy an encrypted file without the password set + win_copy: + src: '{{role_path}}/files-vault/vault-file' + dest: '{{test_win_copy_path}}\file' + register: fail_copy_encrypted_file + ignore_errors: yes # weird failed_when doesn't work in this case + +- name: assert failure message when copying an encrypted file without the password set + assert: + that: + - fail_copy_encrypted_file|failed + - fail_copy_encrypted_file.msg == 'A vault password or secret must be specified to decrypt {{role_path}}/files-vault/vault-file' + +- name: fail to copy a directory with an encrypted file without the password + win_copy: + src: '{{role_path}}/files-vault' + dest: '{{test_win_copy_path}}' + register: fail_copy_directory_with_enc_file + ignore_errors: yes + +- name: assert failure message when copying a directory that contains an encrypted file without the password set + assert: + that: + - fail_copy_directory_with_enc_file|failed + - fail_copy_directory_with_enc_file.msg == 'A vault password or secret must be specified to decrypt {{role_path}}/files-vault/vault-file' + - name: copy with content (check mode) win_copy: content: a