Repository: ansible/ansible-modules-core

Issue:
docker_service module giving incomplete error message.
##### ISSUE TYPE
- Bug Report
##### COMPONENT NAME

docker_service
##### ANSIBLE VERSION

```
ansible 2.1.0.0
```
##### CONFIGURATION

Standard config, running with -vvv verbosity.
##### OS / ENVIRONMENT

RHEL 7.1
##### SUMMARY

We are getting intermittent errors when invoking docker-compose through the docker-service module. The errors themselves are (most likely) unrelated to the docker_service module but we have no way to debug the errors since the docker_service module returns a generic "Error bring foo_app up -Encountered errors while bringing up the project." If you look at docker-compose errors from people running it manually, you'll see that there are typically several preceding error messages before the "Encountered errors while bringing up the project." error ([example](http://stackoverflow.com/questions/39093556/docker-compose-up-encountered-errors-while-bringing-up-the-project)) which actually have some useful troubleshooting information.

Is there anyway to get those additional error lines to be returned by the docker-compose module?
##### STEPS TO REPRODUCE

Run the docker-service module and intentionally cause some error to occur when it tries to invoke docker-compose on the remote machine. You'll see the Ansible output only gives the generic and mostly useless "Encountered errors while bringing up the project." message.
##### EXPECTED RESULTS

Expected to get complete, multi-line error message from docker compose.
##### ACTUAL RESULTS

No multi-line erorr message, only generic "Encountered errors while bringing up the project." message.



Discussion:
@kennethmyers, we need more information to resolve this issue. For that reason, we put this Issue into the 'needs_info' state.

Here are the required items we could not find in your description:
- issue type

When you have filled in the missing data, we will notify the module maintainer for further action.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)

<!--- boilerplate: issue_needs_info --->

@ansible, ping. This issue is waiting on your response.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)

<!--- boilerplate: issue_notify_maintainer --->

@kennethmyers, the error handling was improved with [this commit](https://github.com/ansible/ansible-modules-core/commit/5ab1a6f88dea24eafa6e61114e5687fc7f2b7704).

Can you try running from source to see if you get more useful error messages?

needs_info

That commit just looks like it's changing some of the wording of the error
message, not actually returning more info. I could be mistaken. I'll give it a shot.

On Oct 3, 2016 11:50 AM, "Chris Houseknecht" notifications@github.com
wrote:

> @kennethmyers https://github.com/kennethmyers, the error handling was
> improved with this commit
> https://github.com/ansible/ansible-modules-core/commit/5ab1a6f88dea24eafa6e61114e5687fc7f2b7704
> .
> 
> Can you try running from source to see if you get more useful error
> messages?
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> https://github.com/ansible/ansible-modules-core/issues/5117#issuecomment-251159941,
> or mute the thread
> https://github.com/notifications/unsubscribe-auth/AAktwtfZTnZYyd0SvRpqCXkvdOK8P_gAks5qwTJogaJpZM4KLxhg
> .

@chouseknecht Unfortunately that commit doesn't work and just changes the format/wording of the error message.

Still looking into this, but looking at where the exception is being thrown in Docker compose, it looks like it might not be possible to get the actual errors that occurred, only the generic exception it throws:

https://github.com/docker/compose/blob/e9d62e8404f172fc9f4606f582ddfc80c73f622e/compose/project.py#L404

As you can see in the above code, the presence of errors triggers the exception that we are seeing Ansible report, but it appears we don't have access to the specific errors themselves using the docker-compose Python modules directly.

Not sure what can be done about this. The workaround for now is to invoke docker-compose on the remote machine using the shell module, otherwise no meaningful error message will be reported on failure.

Any thoughts?


Resolving patch:
diff --git a/cloud/docker/docker_service.py b/cloud/docker/docker_service.py
--- a/cloud/docker/docker_service.py
+++ b/cloud/docker/docker_service.py
@@ -452,6 +452,9 @@
 HAS_COMPOSE_EXC = None
 MINIMUM_COMPOSE_VERSION = '1.7.0'
 
+import sys
+import re
+
 try:
     import yaml
 except ImportError as exc:
@@ -463,6 +466,7 @@
 
 try:
     from compose import __version__ as compose_version
+    from compose.project import ProjectError
     from compose.cli.command import project_from_options
     from compose.service import ConvergenceStrategy, NoSuchImageError
     from compose.cli.main import convergence_strategy_from_opts, build_action_from_opts, image_type_from_opt
@@ -473,6 +477,7 @@
     DEFAULT_TIMEOUT = 10
 
 from ansible.module_utils.docker_common import *
+from contextlib import contextmanager
 
 
 AUTH_PARAM_MAPPING = {
@@ -484,7 +489,32 @@
     u'tls_verify': u'--tlsverify'
 }
 
-    
+
+@contextmanager
+def stdout_redirector(path_name):
+    old_stdout = sys.stdout  
+    fd = open(path_name, 'w')
+    sys.stdout = fd
+    try:
+        yield
+    finally:
+        sys.stdout = old_stdout    
+
+def get_stdout(path_name):
+    full_stdout = ''
+    last_line = '' 
+    with open(path_name, 'r') as fd:
+        for line in fd:
+            # strip terminal format/color chars
+            new_line = re.sub(r'\x1b\[.+m', '', line.encode('ascii'))
+            full_stdout += new_line
+            if new_line.strip():
+               # Assuming last line contains the error message 
+               last_line = new_line.strip().encode('utf-8')
+    fd.close()
+    os.remove(path_name)
+    return full_stdout, last_line
+
 class ContainerManager(DockerBaseClass):
 
     def __init__(self, client):
@@ -649,19 +679,25 @@ def cmd_up(self):
                     result['actions'].append(result_action)
 
         if not self.check_mode and result['changed']:
+            _, fd_name = tempfile.mkstemp(prefix="ansible")
             try:
-                do_build = build_action_from_opts(up_options)
-                self.log('Setting do_build to %s' % do_build)
-                self.project.up(
-                    service_names=service_names,
-                    start_deps=start_deps,
-                    strategy=converge,
-                    do_build=do_build,
-                    detached=detached,
-                    remove_orphans=self.remove_orphans,
-                    timeout=self.timeout)
+                with stdout_redirector(fd_name):
+                    do_build = build_action_from_opts(up_options)
+                    self.log('Setting do_build to %s' % do_build)
+                    self.project.up(
+                        service_names=service_names,
+                        start_deps=start_deps,
+                        strategy=converge,
+                        do_build=do_build,
+                        detached=detached,
+                        remove_orphans=self.remove_orphans,
+                        timeout=self.timeout)
             except Exception as exc:
-                self.client.fail("Error starting project - %s" % str(exc))
+                full_stdout, last_line= get_stdout(fd_name)
+                self.client.module.fail_json(msg="Error starting project %s" % str(exc), module_stderr=last_line,
+                                             module_stdout=full_stdout)
+            else:
+                get_stdout(fd_name)
 
         if self.stopped:
             stop_output = self.cmd_stop(service_names)
@@ -863,13 +899,17 @@ def cmd_stop(self, service_names):
                         short_id=container.short_id
                     ))
                 result['actions'].append(service_res)
-
         if not self.check_mode and result['changed']:
+            _, fd_name = tempfile.mkstemp(prefix="ansible")
             try:
-                self.project.stop(service_names=service_names, timeout=self.timeout)
+                with stdout_redirector(fd_name):
+                    self.project.stop(service_names=service_names, timeout=self.timeout)
             except Exception as exc:
-                self.client.fail("Error stopping services for %s - %s" % (self.project.name, str(exc)))
-
+                full_stdout, last_line = get_stdout(fd_name)
+                self.client.module.fail_json(msg="Error stopping project %s" % str(exc), module_stderr=last_line,
+                                             module_stdout=full_stdout)
+            else:
+                get_stdout(fd_name)
         return result
 
     def cmd_restart(self, service_names):
@@ -894,11 +934,16 @@ def cmd_restart(self, service_names):
                 result['actions'].append(service_res)
          
         if not self.check_mode and result['changed']:
+            _, fd_name = tempfile.mkstemp(prefix="ansible")
             try:
-                self.project.restart(service_names=service_names, timeout=self.timeout)
+                with stdout_redirector(fd_name):
+                    self.project.restart(service_names=service_names, timeout=self.timeout)
             except Exception as exc:
-                self.client.fail("Error restarting services for %s - %s" % (self.project.name, str(exc)))
-
+                full_stdout, last_line = get_stdout(fd_name)
+                self.client.module.fail_json(msg="Error restarting project %s" % str(exc), module_stderr=last_line,
+                                             module_stdout=full_stdout)
+            else:
+                get_stdout(fd_name)
         return result
 
     def cmd_scale(self):
@@ -906,7 +951,6 @@ def cmd_scale(self):
             changed=False,
             actions=[]
         )
-
         for service in self.project.services:
             if service.name in self.scale:
                 service_res = dict(


Test patch: