With some file types, simple unified diff is not very helpful. One obvious example is binary files, but even certain ostensibly textual ones -- such as SSL-certificates (.pem) -- fall into this category too.

So, can a task specify its own method of displaying differences? Something like:

- name: Update SSL certificate
  copy:
    src: etc/ssl/mycert.pem
    dest: /etc/ssl/mycert.pem
  diff:
    exec: >
      openssl x509 -in {{ old }} -noout -text > {{ old }}.txt
      openssl x509 -in {{ new }} -noout -text > {{ new }}.txt
      diff -U2 {{ old }}.txt {{ new }}.txt
      rm -f {{ old }}.txt {{ new }}.txt

?


@konstantin-suvorov's "hack" is wonderful, but it seems like it would only work for modules, which already have a diff-method. Unfortunately, my other use-case involves a module (command), which does not.

In particular, I invoke /usr/bin/apt-get update on Ubuntu, which updates apt's cache-files. I'd like to dump the cache's content in text form before and after to see, what the update has changed -- if anything...

up vote 4 down vote accepted

There's no out-of-the box solution. I was curious to challenge this.

Here's custom stdout callback (place into ./callback_plugins/diffhack.py).
It is based on default stdout callback default.py.

from ansible.plugins.callback.default import CallbackModule as DefaultCallback
from subprocess import check_output, CalledProcessError, STDOUT
from tempfile import mkstemp
import os

try:
    from __main__ import display
except ImportError:
    display = None

class CallbackModule(DefaultCallback):

    def v2_on_file_diff(self, result):
        def process_diff(diff, cmd):
            for d in diff:
                for ab in ('after','before'):
                    fd, fn = mkstemp()
                    print fn
                    with open(fn, 'w') as f:
                        f.write(d[ab])
                    try:
                        new_cmd = cmd.replace('%s',fn)
                        res = check_output(new_cmd, stderr=STDOUT, shell=True)
                    except CalledProcessError as e:
                        display.warning('Error occured while calling prediff_cmd "{}": (Code {}) {}'.format(cmd, e.returncode, e.output))
                        res = None
                    os.unlink(fn)
                    if res:
                        d[ab] = res
            return diff

        if 'prediff_cmd' in result._task_fields['vars']:
            prediff_cmd = result._task_fields['vars']['prediff_cmd']
            if result._task.loop and 'results' in result._result:
                for res in result._result['results']:
                    if 'diff' in res and res['diff'] and res.get('changed', False):
                        res['diff'] = process_diff(res['diff'], prediff_cmd)
            elif 'diff' in result._result and result._result['diff'] and result._result.get('changed', False):
                result._result['diff'] = process_diff(result._result['diff'], prediff_cmd)
        return super(CallbackModule, self).v2_on_file_diff(result)

Then you can set prediff_cmd as task's variable with %s being replaced with temporary file:

- copy:
    src: cert.pem
    dest: /tmp/cert.pem
  vars:
    prediff_cmd: openssl x509 -in %s -noout -text | head -n 10

And set default callback, for example:

ANSIBLE_STDOUT_CALLBACK=diffhack ansible-playbook -vv --check --diff test.yml

To see this result:

TASK [copy] ***************************
/var/folders/4c/4t2731dn3csd9b4sv4d2xm5m0000gn/T/tmpS5fOdj
/var/folders/4c/4t2731dn3csd9b4sv4d2xm5m0000gn/T/tmpS4UM7N
--- before: /tmp/cert.pem
+++ after: /path/to/local/cert.pem
@@ -2,9 +2,9 @@
     Data:
         Version: 3 (0x2)
         Serial Number:
-            aa:fc:53:d8:29:d2:8a:58
+            d2:05:f8:5c:61:ff:9e:d3
     Signature Algorithm: sha256WithRSAEncryption
         Issuer: CN=localhost
         Validity
-            Not Before: Oct 13 07:32:01 2017 GMT
-            Not After : Oct 13 07:32:01 2018 GMT
+            Not Before: Oct 13 07:32:25 2017 GMT
+            Not After : Oct 13 07:32:25 2018 GMT
  • This is awesome, but what about modules, which have no diff-method at all now? Like command or shell, for example? I'll update my question with another example. – Mikhail T. Oct 16 '17 at 19:58
  • 1
    In this case I'd opted to write my custom module with desired behaviour. I see no easy to use extension to introduce diff. – Konstantin Suvorov Oct 17 '17 at 6:04

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.