From 5d1d9f15058f01115f2bb551e445e5d02c535afc Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Sun, 26 Jul 2015 22:29:56 -0400 Subject: fixed diff output to be as it was in 1.x, copy and template now use the same functions to do difs. --- lib/ansible/plugins/action/__init__.py | 50 ++++++++++++++++++++++++++++++++ lib/ansible/plugins/action/copy.py | 41 -------------------------- lib/ansible/plugins/action/template.py | 21 ++++---------- lib/ansible/plugins/callback/__init__.py | 34 +++++++++++++++++++++- lib/ansible/plugins/callback/default.py | 4 +++ lib/ansible/plugins/callback/minimal.py | 4 +++ 6 files changed, 97 insertions(+), 57 deletions(-) diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py index 0ba8734d3c..87327ed2b4 100644 --- a/lib/ansible/plugins/action/__init__.py +++ b/lib/ansible/plugins/action/__init__.py @@ -20,9 +20,11 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type from six.moves import StringIO +import base64 import json import os import random +import stat import sys import tempfile import time @@ -480,3 +482,51 @@ class ActionBase: return fnd return None + + def _get_diff_data(self, tmp, destination, source, task_vars, source_file=True): + + diff = {} + self._display.debug("Going to peek to see if file has changed permissions") + peek_result = self._execute_module(module_name='file', module_args=dict(path=destination, diff_peek=True), task_vars=task_vars, persist_files=True) + + if not('failed' in peek_result and peek_result['failed']) or peek_result.get('rc', 0) == 0: + + if peek_result['state'] == 'absent': + diff['before'] = '' + elif peek_result['appears_binary']: + diff['dst_binary'] = 1 + elif peek_result['size'] > C.MAX_FILE_SIZE_FOR_DIFF: + diff['dst_larger'] = C.MAX_FILE_SIZE_FOR_DIFF + else: + self._display.debug("Slurping the file %s" % source) + dest_result = self._execute_module(module_name='slurp', module_args=dict(path=destination), task_vars=task_vars, persist_files=True) + if 'content' in dest_result: + dest_contents = dest_result['content'] + if dest_result['encoding'] == 'base64': + dest_contents = base64.b64decode(dest_contents) + else: + raise AnsibleError("unknown encoding in content option, failed: %s" % dest_result) + diff['before_header'] = destination + diff['before'] = dest_contents + + if source_file: + self._display.debug("Reading local copy of the file %s" % source) + try: + src = open(source) + src_contents = src.read(8192) + st = os.stat(source) + except Exception as e: + raise AnsibleError("Unexpected error while reading source (%s) for diff: %s " % (source, str(e))) + if "\x00" in src_contents: + diff['src_binary'] = 1 + elif st[stat.ST_SIZE] > C.MAX_FILE_SIZE_FOR_DIFF: + diff['src_larger'] = C.MAX_FILE_SIZE_FOR_DIFF + else: + diff['after_header'] = source + diff['after'] = src.read() + else: + self._display.debug("source of file passed in") + diff['after_header'] = 'dynamically generated' + diff['after'] = source + + return diff diff --git a/lib/ansible/plugins/action/copy.py b/lib/ansible/plugins/action/copy.py index fc4727d3aa..715d883057 100644 --- a/lib/ansible/plugins/action/copy.py +++ b/lib/ansible/plugins/action/copy.py @@ -19,11 +19,9 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import base64 import json import os import pipes -import stat import tempfile from ansible import constants as C @@ -238,9 +236,6 @@ class ActionModule(ActionBase): ) ) - if self._play_context.check_mode: - new_module_args['CHECKMODE'] = True - # Execute the file module. module_return = self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars, delete_remote_tmp=delete_remote_tmp) module_executed = True @@ -286,42 +281,6 @@ class ActionModule(ActionBase): f.close() return content_tempfile - def _get_diff_data(self, tmp, destination, source, task_vars): - peek_result = self._execute_module(module_name='file', module_args=dict(path=destination, diff_peek=True), task_vars=task_vars, persist_files=True) - if 'failed' in peek_result and peek_result['failed'] or peek_result.get('rc', 0) != 0: - return {} - - diff = {} - if peek_result['state'] == 'absent': - diff['before'] = '' - elif peek_result['appears_binary']: - diff['dst_binary'] = 1 - elif peek_result['size'] > C.MAX_FILE_SIZE_FOR_DIFF: - diff['dst_larger'] = C.MAX_FILE_SIZE_FOR_DIFF - else: - dest_result = self._execute_module(module_name='slurp', module_args=dict(path=destination), task_vars=task_vars, tmp=tmp, persist_files=True) - if 'content' in dest_result: - dest_contents = dest_result['content'] - if dest_result['encoding'] == 'base64': - dest_contents = base64.b64decode(dest_contents) - else: - raise Exception("unknown encoding, failed: %s" % dest_result) - diff['before_header'] = destination - diff['before'] = dest_contents - - src = open(source) - src_contents = src.read(8192) - st = os.stat(source) - if "\x00" in src_contents: - diff['src_binary'] = 1 - elif st[stat.ST_SIZE] > C.MAX_FILE_SIZE_FOR_DIFF: - diff['src_larger'] = C.MAX_FILE_SIZE_FOR_DIFF - else: - src.seek(0) - diff['after_header'] = source - diff['after'] = src.read() - - return diff def _remove_tempfile_if_content_defined(self, content, content_tempfile): if content is not None: diff --git a/lib/ansible/plugins/action/template.py b/lib/ansible/plugins/action/template.py index 7c14ea1686..81b291ff49 100644 --- a/lib/ansible/plugins/action/template.py +++ b/lib/ansible/plugins/action/template.py @@ -124,20 +124,15 @@ class ActionModule(ActionBase): # Error from remote_checksum is a dict. Valid return is a str return remote_checksum + diff = {} + new_module_args = self._task.args.copy() + if local_checksum != remote_checksum: dest_contents = '' # if showing diffs, we need to get the remote value if self._play_context.diff: - # using persist_files to keep the temp directory around to avoid needing to grab another - my_args = dict(path=dest) - dest_result = self._execute_module(module_name='slurp', module_args=my_args, task_vars=task_vars, persist_files=True) - if 'content' in dest_result: - dest_contents = dest_result['content'] - if dest_result['encoding'] == 'base64': - dest_contents = base64.b64decode(dest_contents) - else: - raise Exception("unknown encoding, failed: %s" % dest_result) + diff = self._get_diff_data(tmp, dest, resultant, task_vars, source_file=False) if not self._play_context.check_mode: # do actual work thorugh copy xfered = self._transfer_data(self._connection._shell.join_path(tmp, 'source'), resultant) @@ -147,7 +142,6 @@ class ActionModule(ActionBase): self._remote_chmod('a+r', xfered, tmp) # run the copy module - new_module_args = self._task.args.copy() new_module_args.update( dict( src=xfered, @@ -161,7 +155,8 @@ class ActionModule(ActionBase): result=dict(changed=True) if result.get('changed', False) and self._play_context.diff: - result['diff'] = dict(before=dest_contents, after=resultant, before_header=dest, after_header=source) + result['diff'] = diff + # result['diff'] = dict(before=dest_contents, after=resultant, before_header=dest, after_header=source) return result @@ -172,7 +167,6 @@ class ActionModule(ActionBase): # the module to follow links. When doing that, we have to set # original_basename to the template just in case the dest is # a directory. - new_module_args = self._task.args.copy() new_module_args.update( dict( src=None, @@ -181,8 +175,5 @@ class ActionModule(ActionBase): ), ) - if self._play_context.check_mode: - new_module_args['CHECKMODE'] = True - return self._execute_module(module_name='file', module_args=new_module_args, task_vars=task_vars) diff --git a/lib/ansible/plugins/callback/__init__.py b/lib/ansible/plugins/callback/__init__.py index 8f484caad2..0aad358cdb 100644 --- a/lib/ansible/plugins/callback/__init__.py +++ b/lib/ansible/plugins/callback/__init__.py @@ -20,8 +20,11 @@ from __future__ import (absolute_import, division) __metaclass__ = type import json +import difflib +import warnings from ansible import constants as C +from ansible.utils.unicode import to_unicode __all__ = ["CallbackBase"] @@ -54,6 +57,35 @@ class CallbackBase: for warning in res['warnings']: self._display.warning(warning) + def _get_diff(self, diff): + try: + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + ret = [] + if 'dst_binary' in diff: + ret.append("diff skipped: destination file appears to be binary\n") + if 'src_binary' in diff: + ret.append("diff skipped: source file appears to be binary\n") + if 'dst_larger' in diff: + ret.append("diff skipped: destination file size is greater than %d\n" % diff['dst_larger']) + if 'src_larger' in diff: + ret.append("diff skipped: source file size is greater than %d\n" % diff['src_larger']) + if 'before' in diff and 'after' in diff: + if 'before_header' in diff: + before_header = "before: %s" % diff['before_header'] + else: + before_header = 'before' + if 'after_header' in diff: + after_header = "after: %s" % diff['after_header'] + else: + after_header = 'after' + differ = difflib.unified_diff(to_unicode(diff['before']).splitlines(True), to_unicode(diff['after']).splitlines(True), before_header, after_header, '', '', 10) + for line in list(differ): + ret.append(line) + return u"".join(ret) + except UnicodeDecodeError: + return ">> the files are different, but the diff library cannot compare unicode strings" + def set_play_context(self, play_context): pass @@ -118,7 +150,7 @@ class CallbackBase: pass def on_file_diff(self, host, diff): - self._display.display(self._dump_results(diff)) + pass ####### V2 METHODS, by default they call v1 counterparts if possible ###### def v2_on_any(self, *args, **kwargs): diff --git a/lib/ansible/plugins/callback/default.py b/lib/ansible/plugins/callback/default.py index b6e56f99fd..f50b3e9885 100644 --- a/lib/ansible/plugins/callback/default.py +++ b/lib/ansible/plugins/callback/default.py @@ -109,3 +109,7 @@ class CallbackModule(CallbackBase): msg = "PLAY [%s]" % name self._display.banner(msg) + + def v2_on_file_diff(self, result): + if 'diff' in result._result: + self._display.display(self._get_diff(result._result['diff'])) diff --git a/lib/ansible/plugins/callback/minimal.py b/lib/ansible/plugins/callback/minimal.py index de01d98c6d..c14107694b 100644 --- a/lib/ansible/plugins/callback/minimal.py +++ b/lib/ansible/plugins/callback/minimal.py @@ -75,3 +75,7 @@ class CallbackModule(CallbackBase): def v2_runner_on_unreachable(self, result): self._display.display("%s | UNREACHABLE!" % result._host.get_name(), color='yellow') + + def v2_on_file_diff(self, result): + if 'diff' in result._result: + self._display.display(self._get_diff(result._result['diff'])) -- cgit v1.2.1