Define what `diff` means for Ansible task

I was inspired to dig into this topic by this ServerFault question.

So you have some Ansible task that supports diff output (e.g. copy).
But imagine that you copy files that are not human readable by default (like pem-certificates, for example).
In this case Ansible will print some base64 "garbage" as task's diff:

--- before: /tmp/cert.pem
+++ after: /path/to/cert.pem
@@ -1,13 +1,13 @@
 -----BEGIN CERTIFICATE-----
-MIIB7DCCAVWgAwIBAgIJAMUAMA8xDTALBgNV
-BAMMBHRlc3QwHhcNMTcxMDQ1WjAPMQ0wCwYD
-VQQDDAR0ZXN0MIGfMA0GCSCmJCHr6m0CCEjE
-mtDD1EHUAo8y0jN9lYdF251k07WH1MKVh2tQ
-nxywqt1ktrqI/CPJqItKAerOh897GkOw06Rk
-/ufGeYFxr/Ff+fVaL4qq0g==
+MIIB7DCCAVWgAwIBAgIJCwUAMA8xDTALBgNV
+BAMMBHRlc3QwHhcNMTcxODU0WjAPMQ0wCwYD
+VQQDDAR0ZXN0MIGfMA0GgQChH7tIRNrbLEz6
+kPwL+YijDVXgHUjp2rc9j0pbs04s/oJ9L3ah
+bwFcpP3FmyabwiWgVOR8KyaITlyBuExhH83D
+zPf/JYnGvJw/q94q6/6cEg==
 -----END CERTIFICATE-----

If we make things simple, every module that support diff returns before and after strings, that are processed with Python's difflib.unified_diff in default stdout callback.

So to tackle our problem we either need to:

  • alter before and after strings on their way from module
  • alter before and after strings while they are processed by callback

If you need to modify a single module, you can write an action plugin on top of the module, or even subclass the original action plugin (like copy) that will alter those strings.

But if we want a more general approach and be able to override diff processing for any task/module, we need to modify callback function.

Here is prediff.py callback plugin that subclasses Ansible's default stdout callback.

The only method that is overridden is v2_on_file_diff. I check for prediff_cmd in task variables and if it's there, execute this command to modify before/after strings.

This way if there is no prediff_cmd variable defined everything work as usual, but if prediff_cmd is there before/after are preprocessed with this command before executing unified_diff.

To allow easy use of prediff_cmd I dump strings into temporary file with its path available as %s placeholder.

Example playbook may look like:

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

Next we set our prediff callback as stdout plugin:

ANSIBLE_STDOUT_CALLBACK=prediff ansible-playbook --check --diff test.yml  

And get nice human readable diff:

--- before: /tmp/cert.pem
+++ after: /path/to/cert.pem
@@ -2,9 +2,9 @@
     Data:
         Version: 3 (0x2)
         Serial Number:
-            c6:40:43:a5:99:0d:63:ac
+            db:e2:e2:8e:a2:b2:49:1f
     Signature Algorithm: sha256WithRSAEncryption
         Issuer: CN=test
         Validity
-            Not Before: Oct 21 09:18:45 2017 GMT
-            Not After : Oct 21 09:18:45 2018 GMT
+            Not Before: Oct 21 09:18:54 2017 GMT
+            Not After : Oct 21 09:18:54 2018 GMT