Repository: ansible/ansible
Pull request: catch the case that cowsay is broken
fixes https://github.com/ansible/ansible/issues/72582

  add changelog
  raise AnsibleError for broken cowsay
  add test for broken cowsay

Rebase and alteration of  #72592


##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
- Bugfix Pull Request

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below -->
display

Problem statement:
broken cowsay leads to unusable ansible
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
<!--- Explain the problem briefly below -->

Unbelievable, but true: `cowsay` is broken on my Ubuntu 20.04 `Encode.c: loadable library and perl binaries are mismatched (got handshake key 0xdb00080, needed 0xcd00080)`

This leads to missing lines in the output (see below). I tried hard to search on popular search engines for a fix, without luck. 

Main point that this is an issue that is really hard to resolve for less experienced users. 

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->

`ansible-playbook`

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```paste below
  config file = None
  configured module search path = [u'/home/berntm/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python2.7/dist-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.18 (default, Aug  4 2020, 11:16:42) [GCC 9.3.0]
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```paste below

```

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->


##### STEPS TO REPRODUCE

That's the odd thing. I tried to run `alias cowsay="exit 1"` and also replaced /usr/bin/cowsay with a script that just `exit 1`. But in both cases then the lines are still there. 

<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->

<!--- Paste example playbooks or commands between quotes below -->
```yaml

```

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- Describe what you expected to happen when running the steps above -->

```
PLAY [localhost] **************************************************************************

TASK [Gathering Facts] ********************************************************************
ok: [localhost]
...
```


##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes -->

```paste below




ok: [localhost]
...
```

Hints from issue discussion:
Files identified in the description:
None

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
@bernt-matthias: Greetings! Thanks for taking the time to open this issue. In order for the community to handle your issue effectively, we need a bit more information.

Here are the items we could not find in your description:
- component name

Please set the description of this issue with an appropriate template from:
https://github.com/ansible/ansible/tree/devel/.github/ISSUE_TEMPLATE

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: issue_missing_data --->
Files identified in the description:
* `lib/ansible/playbook`

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
Should be an easy fix I think:

```diff
diff --git a/lib/ansible/utils/display.py b/lib/ansible/utils/display.py
index e268a63c9f..17a3f39ff4 100644
--- a/lib/ansible/utils/display.py
+++ b/lib/ansible/utils/display.py
@@ -225,6 +225,8 @@ class Display(with_metaclass(Singleton, object)):
             try:
                 cmd = subprocess.Popen([self.b_cowsay, "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                 (out, err) = cmd.communicate()
+                if cmd.returncode:
+                    raise Exception
                 self.cows_available = set([to_text(c) for c in out.split()])
                 if C.ANSIBLE_COW_ACCEPTLIST and any(C.ANSIBLE_COW_ACCEPTLIST):
                     self.cows_available = set(C.ANSIBLE_COW_ACCEPTLIST).intersection(self.cows_available)
```
Thanks @sivel. That works for me. I opened a PR.

Commit message: catch the case that cowsay is broken

fixes https://github.com/ansible/ansible/issues/72582

  add changelog
  raise AnsibleError for broken cowsay
  add test for broken cowsay

(cherry picked from commit 49e6e81a4be8e0fea6d4e8eff92512b753d872bf)

Patch to review:
diff --git a/changelogs/fragments/72592-catch-broken-cowsay.yaml b/changelogs/fragments/72592-catch-broken-cowsay.yaml
new file mode 100644
index 00000000000000..0fd5507c38496c
--- /dev/null
+++ b/changelogs/fragments/72592-catch-broken-cowsay.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+  - catch the case that cowsay is broken which would lead to missing output
\ No newline at end of file
diff --git a/lib/ansible/utils/display.py b/lib/ansible/utils/display.py
index 86237ef85a660b..4011c8186c173f 100644
--- a/lib/ansible/utils/display.py
+++ b/lib/ansible/utils/display.py
@@ -220,6 +220,9 @@ def __init__(self, verbosity=0):
                 cmd = subprocess.Popen([self.b_cowsay, "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                 (out, err) = cmd.communicate()
                 self.cows_available = {to_text(c) for c in out.split()}
+                if cmd.returncode:
+                    raise Exception
+                self.cows_available = set([to_text(c) for c in out.split()])
                 if C.ANSIBLE_COW_ACCEPTLIST and any(C.ANSIBLE_COW_ACCEPTLIST):
                     self.cows_available = set(C.ANSIBLE_COW_ACCEPTLIST).intersection(self.cows_available)
             except Exception:
diff --git a/test/units/utils/display/test_broken_cowsay.py b/test/units/utils/display/test_broken_cowsay.py
new file mode 100644
index 00000000000000..9fa54d19decee7
--- /dev/null
+++ b/test/units/utils/display/test_broken_cowsay.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+from ansible.utils.display import Display
+from mock import MagicMock
+
+
+def test_display_with_fake_cowsay_binary(capsys, mocker):
+    mocker.patch("ansible.constants.ANSIBLE_COW_PATH", "./cowsay.sh")
+
+    def mock_communicate(input=None, timeout=None):
+        return b"", b""
+
+    mock_popen = MagicMock()
+    mock_popen.return_value.communicate = mock_communicate
+    mock_popen.return_value.returncode = 1
+    mocker.patch("subprocess.Popen", mock_popen)
+
+    display = Display()
+    assert not hasattr(display, "cows_available")
+    assert display.b_cowsay is False


Code review comments:
diff_hunk: @@ -220,6 +220,9 @@ def __init__(self, verbosity=0):
                 cmd = subprocess.Popen([self.b_cowsay, "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                 (out, err) = cmd.communicate()
                 self.cows_available = {to_text(c) for c in out.split()}
path: lib/ansible/utils/display.py
text: @user1:
This line can be removed