Repository: ansible/ansible

Issue:
default and minimal callback display warnings in an inconsistent manner, depending on task results
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
default and minimal stdout callback

##### ANSIBLE VERSION
```
ansible 2.3.0 (devel c86a17b7a0) last updated 2017/02/07 10:58:10 (GMT +200)
  config file = /home/pilou/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```

##### CONFIGURATION
In `ansible.cfg`:
`stdout_callback = minimal`
or
`stdout_callback = default`

Environment variable `ANSIBLE_STDOUT_CALLBACK` can be set too.

##### OS / ENVIRONMENT
N/A

##### SUMMARY
The way warnings are presented to the user aren't consistent:
* when a play is successful or when an item failed, warnings are displayed using a specific color and are prefixed by `[WARNING]: `
![success_with_warning_white](https://cloud.githubusercontent.com/assets/1356830/22732426/668ab472-edee-11e6-8010-44dbbf3d5bee.png)

```
TASK [command] ********************************************************************************************************************************
changed: [localhost]
 [WARNING]: Consider using file module with state=touch rather than running touch
```
* when a play failed, warnings are displayed with the error message, using the same color and without prefix:
![failure_with_warning_white](https://cloud.githubusercontent.com/assets/1356830/22732414/5fded054-edee-11e6-8c78-464f9b17c8d9.png)

```
TASK [command] ********************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["touch", "/coincoin"], "delta": "0:00:00.001779", "end": "2017-02-08 10:36:55.994992", "failed": true, "rc": 1, "start": "2017-02-08 10:36:55.993213", "stderr": "touch: cannot touch '/coincoin': Permission denied", "stdout": "", "stdout_lines": [], "warnings": ["Consider using file module with state=touch rather than running touch"]}
```

Test playbook:
`ANSIBLE_STDOUT_CALLBACK=default ansible-playbook -l localhost --extra-vars 'test=/coincoin' playbooks/test.yml` or `ANSIBLE_STDOUT_CALLBACK=default ansible-playbook -l localhost --extra-vars 'test=/tmp/coincoin' playbooks/test.yml`
```yaml
---
- hosts: localhost
  tasks:
  - command: 'touch {{ test }}'
```

##### EXPECTED RESULTS
In all cases, warning should be displayed to the user using the same format/color. For a failed run, the output should be:
![expected_result_white](https://cloud.githubusercontent.com/assets/1356830/22732399/5351337c-edee-11e6-8408-55415ba726c7.png)
```
TASK [command] ********************************************************************************************************************************
 [WARNING]: Consider using file module with state=touch rather than running touch
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["touch", "/coincoin"], "delta": "0:00:00.001779", "end": "2017-02-08 10:36:55.994992", "failed": true, "rc": 1, "start": "2017-02-08 10:36:55.993213", "stderr": "touch: cannot touch '/coincoin': Permission denied", "stdout": "", "stdout_lines": []}
```


Discussion:

Resolving patch:
diff --git a/lib/ansible/plugins/callback/default.py b/lib/ansible/plugins/callback/default.py
--- a/lib/ansible/plugins/callback/default.py
+++ b/lib/ansible/plugins/callback/default.py
@@ -56,6 +56,8 @@ def v2_runner_on_failed(self, result, ignore_errors=False):
 
             self._display.display(msg, color=C.COLOR_ERROR)
 
+        self._handle_warnings(result._result)
+
         if result._task.loop and 'results' in result._result:
             self._process_items(result)
 
@@ -92,6 +94,8 @@ def v2_runner_on_ok(self, result):
                 msg = "ok: [%s]" % result._host.get_name()
             color = C.COLOR_OK
 
+        self._handle_warnings(result._result)
+
         if result._task.loop and 'results' in result._result:
             self._process_items(result)
         else:
@@ -100,8 +104,6 @@ def v2_runner_on_ok(self, result):
                 msg += " => %s" % (self._dump_results(result._result),)
             self._display.display(msg, color=color)
 
-        self._handle_warnings(result._result)
-
     def v2_runner_on_skipped(self, result):
         if C.DISPLAY_SKIPPED_HOSTS:
             if self._play.strategy == 'free' and self._last_task_banner != result._task._uuid:
@@ -227,8 +229,8 @@ def v2_runner_item_on_failed(self, result):
         else:
             msg += "[%s]" % (result._host.get_name())
 
-        self._display.display(msg + " (item=%s) => %s" % (self._get_item(result._result), self._dump_results(result._result)), color=C.COLOR_ERROR)
         self._handle_warnings(result._result)
+        self._display.display(msg + " (item=%s) => %s" % (self._get_item(result._result), self._dump_results(result._result)), color=C.COLOR_ERROR)
 
     def v2_runner_item_on_skipped(self, result):
         if C.DISPLAY_SKIPPED_HOSTS:
diff --git a/lib/ansible/plugins/callback/minimal.py b/lib/ansible/plugins/callback/minimal.py
--- a/lib/ansible/plugins/callback/minimal.py
+++ b/lib/ansible/plugins/callback/minimal.py
@@ -55,6 +55,8 @@ def v2_runner_on_failed(self, result, ignore_errors=False):
 
             self._display.display(msg, color=C.COLOR_ERROR)
 
+        self._handle_warnings(result._result)
+
         if result._task.action in C.MODULE_NO_JSON and 'module_stderr' not in result._result:
             self._display.display(self._command_generic_msg(result._host.get_name(), result._result, "FAILED"), color=C.COLOR_ERROR)
         else:
@@ -62,6 +64,9 @@ def v2_runner_on_failed(self, result, ignore_errors=False):
 
     def v2_runner_on_ok(self, result):
         self._clean_results(result._result, result._task.action)
+
+        self._handle_warnings(result._result)
+
         if result._task.action in C.MODULE_NO_JSON:
             self._display.display(self._command_generic_msg(result._host.get_name(), result._result, "SUCCESS"), color=C.COLOR_OK)
         else:
@@ -69,7 +74,6 @@ def v2_runner_on_ok(self, result):
                 self._display.display("%s | SUCCESS => %s" % (result._host.get_name(), self._dump_results(result._result, indent=4)), color=C.COLOR_CHANGED)
             else:
                 self._display.display("%s | SUCCESS => %s" % (result._host.get_name(), self._dump_results(result._result, indent=4)), color=C.COLOR_OK)
-            self._handle_warnings(result._result)
 
     def v2_runner_on_skipped(self, result):
         self._display.display("%s | SKIPPED" % (result._host.get_name()), color=C.COLOR_SKIP)


Test patch: