When running ansible scripts, occasionally you wonder why a given task has failed. I found out more than once that it’s commonly a problem with the script, not the engine ;) Finding out exactly where in the script I made the mistake can be more of a challenge.
With the default ansible settings, output can be a bit hard to read. Consider this example: I do quite a bit of patching in my lab, and this almost always requires an upgrade of OPatch (d’oh!). So instead of connecting to each of my hosts and performing the same unzip command over and over again, I thought of using something else. Why not use ansible for this task? It won’t get tired copying/unzipping OPatch to all the destinations I indicate in my configuration. And it won’t introduce a mistake when dealing with the fifth ORACLE_HOME on the third server…
Before replacing $ORACLE_HOME/OPatch with the new version, I want to take a backup of the current OPatch just in case. I don’t want to keep more than 1 backup around in this particular lab environment, so I decided to check for an existing backup first, before creating a new one. If one exists, I remove it. Or at least, that’s the plan.
So I was happily coding away and in my usual trial-and-error approach was ready to test the script I wrote for the first time. Here’s the result (as shown in my 80×24 terminal):
[martin@controller environment]$ ansible-playbook -i inventory.yml broken.yml
PLAY [blogpost] ****************************************************************
TASK [Gathering Facts] *********************************************************
ok: [server1]
TASK [check if there is an old backup] *****************************************
ok: [server1]
TASK [remove old OPatch backup] ************************************************
fatal: [server1]: FAILED! => {"msg": "The conditional check 'backup_present.exis
ts' failed. The error was: error while evaluating conditional (backup_present.ex
ists): 'dict object' has no attribute 'exists'\n\nThe error appears to have been
in '/home/martin/ansible/blogpost/environment/broken.yml': line 20, column 11,
but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe
offending line appears to be:\n\n\n - name: remove old OPatch backup\n
^ here\n"}
PLAY RECAP *********************************************************************
server1 : ok=2 changed=0 unreachable=0 failed=1
[martin@controller environment]$
It doesn’t really matter what I was trying to do here, what matters though is the somewhat illegible formatting of the output. The listing above really shows how the error displayed in my terminal. I haven’t quite understood yet why there are line breaks (\n) in the output that don’t result in a carriage return on screen.
So I did a little bit of digging around and found a global setting named stdout_callback. This is usually defined in /etc/ansible/ansible.cfg which would be bad news for developers if we couldn’t override it. Thankfully you can – using $HOME/.ansible.cfg. Setting stdout_callback to “debug” reveals a much more readable version of the error:
TASK [remove old OPatch backup] ************************************************
fatal: [server1]: FAILED! => {}
MSG:
The conditional check 'backup_present.exists' failed. The error was: error while
evaluating conditional (backup_present.exists): 'dict object' has no attribute
'exists'
The error appears to have been in '/home/martin/ansible/blogpost/environment/bro
ken.yml': line 20, column 11, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- name: remove old OPatch backup
^ here
PLAY RECAP *********************************************************************
server1 : ok=2 changed=0 unreachable=0 failed=1
I find this much easier to read, and by setting stdout_callback to a non-default value in my project directory I don’t break anything inadvertently. It also immediately revealed I wasn’t checking backup_exists.stat.exists, I used backup_exists.exists.
Pretty-printing the output helped me debug the mistake much quicker. Later on, when your script is ready to be deployed it’s probably a good idea not to use the debug callback.
Here’s the contents of ~/.ansible.cfg for your convenience:
$ cat ~/.ansible.cfg [defaults] stdout_callback = debug
This post was written when ansible 2.6.4 was current. The settings should however still be relevant in modern ansible versions, too.
Happy scripting!