Repository: ansible/ansible

Issue:
Modules shell and command only output MODULE FAILURE on error
<!---
Please do not report issues/requests related to Ansible modules here !!

Report them to the appropriate modules-core or modules-extras project:
  - https://github.com/ansible/ansible-modules-core/issues
  - https://github.com/ansible/ansible-modules-extras/issues

Also verify first that your issue/request is not already reported in GitHub
-->


##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the plugin/task/feature -->
command / shell

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
v2.2

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
*hosts*
```ini
problemhost ansible_host=127.0.0.1 ansible_python_interpreter=/foo/bar
```

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
Any

##### SUMMARY
<!--- Explain the problem briefly -->
The shell and command modules do not provide any indication to what the error is, it only outputs **MODULE FAILURE**


##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```
[dag@moria ansible.testing]$ ansible problemhost -m setup 
problemhost | FAILED! => {
    "changed": false, 
    "failed": true, 
    "module_stderr": "/bin/sh: /foo/bar: No such file or directory\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE"
}

[dag@moria ansible.testing]$ ansible problemhost -m command -a date
problemhost | FAILED | rc=0 >>
MODULE FAILURE
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
```
problemhost | FAILED! => {
    "changed": false, 
    "failed": true, 
    "module_stderr": "/bin/sh: /foo/bar: No such file or directory\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE"
}
```

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
problemhost | FAILED | rc=0 >>
MODULE FAILURE

```



Discussion:

Resolving patch:
diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py
--- a/lib/ansible/plugins/action/__init__.py
+++ b/lib/ansible/plugins/action/__init__.py
@@ -716,6 +716,8 @@ def _parse_returned_data(self, res):
                 data['module_stderr'] = res['stderr']
                 if res['stderr'].startswith(u'Traceback'):
                     data['exception'] = res['stderr']
+            if 'rc' in res:
+                data['rc'] = res['rc']
         return data
 
     def _low_level_execute_command(self, cmd, sudoable=True, in_data=None, executable=None, encoding_errors='surrogate_or_replace'):
@@ -797,7 +799,7 @@ def _get_diff_data(self, destination, source, task_vars, source_file=True):
         display.debug("Going to peek to see if file has changed permissions")
         peek_result = self._execute_module(module_name='file', module_args=dict(path=destination, diff_peek=True), task_vars=task_vars, persist_files=True)
 
-        if not('failed' in peek_result and peek_result['failed']) or peek_result.get('rc', 0) == 0:
+        if not peek_result.get('failed', False) or peek_result.get('rc', 0) == 0:
 
             if peek_result['state'] == 'absent':
                 diff['before'] = ''
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
@@ -37,7 +37,7 @@ class CallbackModule(CallbackBase):
     def _command_generic_msg(self, host, result, caption):
         ''' output the result of a command run '''
 
-        buf = "%s | %s | rc=%s >>\n" % (host, caption, result.get('rc',0))
+        buf = "%s | %s | rc=%s >>\n" % (host, caption, result.get('rc', -1))
         buf += result.get('stdout','')
         buf += result.get('stderr','')
         buf += result.get('msg','')
@@ -55,7 +55,7 @@ def v2_runner_on_failed(self, result, ignore_errors=False):
 
             self._display.display(msg, color=C.COLOR_ERROR)
 
-        if result._task.action in C.MODULE_NO_JSON:
+        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:
             self._display.display("%s | FAILED! => %s" % (result._host.get_name(), self._dump_results(result._result, indent=4)), color=C.COLOR_ERROR)
diff --git a/lib/ansible/plugins/callback/oneline.py b/lib/ansible/plugins/callback/oneline.py
--- a/lib/ansible/plugins/callback/oneline.py
+++ b/lib/ansible/plugins/callback/oneline.py
@@ -38,9 +38,9 @@ def _command_generic_msg(self, hostname, result,  caption):
         stdout = result.get('stdout','').replace('\n', '\\n')
         if 'stderr' in result and result['stderr']:
             stderr = result.get('stderr','').replace('\n', '\\n')
-            return "%s | %s | rc=%s | (stdout) %s (stderr) %s" % (hostname, caption, result.get('rc',0), stdout, stderr)
+            return "%s | %s | rc=%s | (stdout) %s (stderr) %s" % (hostname, caption, result.get('rc', -1), stdout, stderr)
         else:
-            return "%s | %s | rc=%s | (stdout) %s" % (hostname, caption, result.get('rc',0), stdout)
+            return "%s | %s | rc=%s | (stdout) %s" % (hostname, caption, result.get('rc', -1), stdout)
 
     def v2_runner_on_failed(self, result, ignore_errors=False):
         if 'exception' in result._result:
@@ -51,7 +51,7 @@ def v2_runner_on_failed(self, result, ignore_errors=False):
             else:
                 msg = "An exception occurred during task execution. The full traceback is:\n" + result._result['exception'].replace('\n','')
 
-            if result._task.action in C.MODULE_NO_JSON:
+            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:
                 self._display.display(msg, color=C.COLOR_ERROR)


Test patch: