Repository: ansible/ansible

Issue:
blockinfile should show diff
##### ISSUE TYPE
 - Feature Idea

##### COMPONENT NAME
blockinfile

##### ANSIBLE VERSION
```
ansible 2.2.2.0
  config file = 
  configured module search path = Default w/o overrides
```

##### CONFIGURATION

##### OS / ENVIRONMENT
N/A

##### SUMMARY
It's very easy to accidentally overwrite changes in content managed via blockinfile because `--diff --check` does not show a diff of the update atm.

##### STEPS TO REPRODUCE
```yaml
---
- hosts: localhost
  tasks:
  - name: original block
    blockinfile:
      dest: test.txt
      create: yes
      block: |
        foo bar
  - name: updated block
    blockinfile:
      dest: test.txt
      create: yes
      block: |
        foo bar baz
```
`ansible-playbook --diff test.yml`

##### EXPECTED RESULTS
Should show the diff between `foo bar` and `foo bar baz`

##### ACTUAL RESULTS
With `-vvvv` it still only shows the new block content.



Discussion:
cc @yaegashi
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
AFAIK, diff doesn't work with blockinfile
@snowsky no, it works with any module that implements it. Template, copy, user, etc, many do.
@bcoca oops, I missed the codes for diff with blockinfile, then that is a bug. ;)
Which file should I take another look?
Didn't see blockinfile implemented diff though

Resolving patch:
diff --git a/lib/ansible/modules/files/blockinfile.py b/lib/ansible/modules/files/blockinfile.py
--- a/lib/ansible/modules/files/blockinfile.py
+++ b/lib/ansible/modules/files/blockinfile.py
@@ -167,6 +167,7 @@
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils._text import to_bytes
 
+
 def write_changes(module, contents, path):
 
     tmpfd, tmpfile = tempfile.mkstemp()
@@ -188,10 +189,10 @@ def write_changes(module, contents, path):
         module.atomic_move(tmpfile, path, unsafe_writes=module.params['unsafe_writes'])
 
 
-def check_file_attrs(module, changed, message):
+def check_file_attrs(module, changed, message, diff):
 
     file_args = module.load_file_common_arguments(module.params)
-    if module.set_file_attributes_if_different(file_args, False):
+    if module.set_file_attributes_if_different(file_args, False, diff=diff):
 
         if changed:
             message += " and "
@@ -241,6 +242,14 @@ def main():
         f.close()
         lines = original.splitlines()
 
+    diff = {'before': '',
+            'after': '',
+            'before_header': '%s (content)' % path,
+            'after_header': '%s (content)' % path}
+
+    if module._diff and original:
+        diff['before'] = original
+
     insertbefore = params['insertbefore']
     insertafter = params['insertafter']
     block = to_bytes(params['block'])
@@ -305,6 +314,10 @@ def main():
             result += b('\n')
     else:
         result = ''
+
+    if module._diff:
+        diff['after'] = result
+
     if original == result:
         msg = ''
         changed = False
@@ -324,10 +337,16 @@ def main():
         write_changes(module, result, path)
 
     if module.check_mode and not path_exists:
-        module.exit_json(changed=changed, msg=msg)
+        module.exit_json(changed=changed, msg=msg, diff=diff)
+
+    attr_diff = {}
+    msg, changed = check_file_attrs(module, changed, msg, attr_diff)
+
+    attr_diff['before_header'] = '%s (file attributes)' % path
+    attr_diff['after_header'] = '%s (file attributes)' % path
 
-    msg, changed = check_file_attrs(module, changed, msg)
-    module.exit_json(changed=changed, msg=msg)
+    difflist = [diff, attr_diff]
+    module.exit_json(changed=changed, msg=msg, diff=difflist)
 
 
 if __name__ == '__main__':


Test patch: