Repository: ansible/ansible

Issue:
Docker connection ignores 'ansible_docker_extra_args' when querying docker server version.
##### ISSUE TYPE
- Bug Report
##### ANSIBLE VERSION

2.1.1.0
##### CONFIGURATION

default
##### OS / ENVIRONMENT

Docker 1.9+
##### SUMMARY

Docker connection ignores 'ansible_docker_extra_args' when querying docker server version.
##### STEPS TO REPRODUCE

Use docker connection without local docker server running and 'ansible_docker_extra_args' set to remote docker host.
##### EXPECTED RESULTS

Should work.
##### ACTUAL RESULTS

An exception occurred during task execution. To see the full traceback, use -vvv. The error was: CalledProcessError: Command '['/usr/bin/docker', 'version', '--format', "'{{.Server.Version}}'"]' returned non-zero exit status 1
fatal: [test]: FAILED! => {"failed": true, "msg": "Unexpected failure during module execution.", "stdout": ""}

[docker-connection-patch.zip](https://github.com/ansible/ansible/files/403783/docker-connection-patch.zip)



Discussion:

Resolving patch:
diff --git a/lib/ansible/plugins/connection/docker.py b/lib/ansible/plugins/connection/docker.py
--- a/lib/ansible/plugins/connection/docker.py
+++ b/lib/ansible/plugins/connection/docker.py
@@ -104,28 +104,43 @@ def __init__(self, play_context, new_stdin, *args, **kwargs):
     def _sanitize_version(version):
         return re.sub('[^0-9a-zA-Z\.]', '', version)
 
-    def _get_docker_version(self):
+    def _old_docker_version(self):
+        cmd_args = []
+        if self._play_context.docker_extra_args:
+            cmd_args += self._play_context.docker_extra_args.split(' ')
+
+        old_version_subcommand = ['version']
+
+        old_docker_cmd = [self.docker_cmd] + cmd_args + old_version_subcommand
+        p = subprocess.Popen(old_docker_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        cmd_output, err = p.communicate()
 
-        cmd = [self.docker_cmd]
+        return old_docker_cmd, cmd_output, err, p.returncode
 
+    def _new_docker_version(self):
+        # no result yet, must be newer Docker version
+        cmd_args = []
         if self._play_context.docker_extra_args:
-            cmd += self._play_context.docker_extra_args.split(' ')
+            cmd_args += self._play_context.docker_extra_args.split(' ')
 
-        cmd += ['version']
+        new_version_subcommand = ['version', '--format', "'{{.Server.Version}}'"]
 
-        cmd_output = subprocess.check_output(cmd)
+        new_docker_cmd = [self.docker_cmd] + cmd_args + new_version_subcommand
+        p = subprocess.Popen(new_docker_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        cmd_output, err = p.communicate()
+        return new_docker_cmd, cmd_output, err, p.returncode
 
-        for line in cmd_output.split('\n'):
-            if line.startswith('Server version:'):  # old docker versions
-                return self._sanitize_version(line.split()[2])
+    def _get_docker_version(self):
 
-        # no result yet, must be newer Docker version
-        new_docker_cmd = [
-            self.docker_cmd,
-            'version', '--format', "'{{.Server.Version}}'"
-        ]
+        cmd, cmd_output, err, returncode = self._old_docker_version()
+        if returncode == 0:
+            for line in cmd_output.split('\n'):
+                if line.startswith('Server version:'):  # old docker versions
+                    return self._sanitize_version(line.split()[2])
 
-        cmd_output = subprocess.check_output(new_docker_cmd)
+        cmd, cmd_output, err, returncode = self._new_docker_version()
+        if returncode:
+            raise AnsibleError('Docker version check (%s) failed: %s' % (cmd, err))
 
         return self._sanitize_version(cmd_output)
 


Test patch:
diff --git a/test/units/plugins/connections/test_connection.py b/test/units/plugins/connections/test_connection.py
--- a/test/units/plugins/connections/test_connection.py
+++ b/test/units/plugins/connections/test_connection.py
@@ -21,7 +21,10 @@
 
 from io import StringIO
 
+import mock
+
 from ansible.compat.tests import unittest
+from ansible.errors import AnsibleError
 from ansible.playbook.play_context import PlayContext
 
 from ansible.plugins.connection import ConnectionBase
@@ -34,8 +37,10 @@
 from ansible.plugins.connection.local import Connection as LocalConnection
 from ansible.plugins.connection.paramiko_ssh import Connection as ParamikoConnection
 from ansible.plugins.connection.ssh import Connection as SSHConnection
+from ansible.plugins.connection.docker import Connection as DockerConnection
 #from ansible.plugins.connection.winrm import Connection as WinRmConnection
 
+
 class TestConnectionBaseClass(unittest.TestCase):
 
     def setUp(self):
@@ -102,5 +107,21 @@ def test_paramiko_connection_module(self):
     def test_ssh_connection_module(self):
         self.assertIsInstance(SSHConnection(self.play_context, self.in_stream), SSHConnection)
 
+    @mock.patch('ansible.plugins.connection.docker.Connection._old_docker_version', return_value=('false', 'garbage', '', 1))
+    @mock.patch('ansible.plugins.connection.docker.Connection._new_docker_version', return_value=('docker version', '1.2.3', '', 0))
+    def test_docker_connection_module_too_old(self, mock_new_docker_verison, mock_old_docker_version):
+        self.assertRaises(AnsibleError, DockerConnection, self.play_context, self.in_stream)
+
+    @mock.patch('ansible.plugins.connection.docker.Connection._old_docker_version', return_value=('false', 'garbage', '', 1))
+    @mock.patch('ansible.plugins.connection.docker.Connection._new_docker_version', return_value=('docker version', '1.3.4', '', 0))
+    def test_docker_connection_module(self, mock_new_docker_verison, mock_old_docker_version):
+        self.assertIsInstance(DockerConnection(self.play_context, self.in_stream), DockerConnection)
+
+    # old version and new version fail
+    @mock.patch('ansible.plugins.connection.docker.Connection._old_docker_version', return_value=('false', 'garbage', '', 1))
+    @mock.patch('ansible.plugins.connection.docker.Connection._new_docker_version', return_value=('false', 'garbage', '', 1))
+    def test_docker_connection_module_wrong_cmd(self, mock_new_docker_version, mock_old_docker_version):
+        self.assertRaises(AnsibleError, DockerConnection, self.play_context, self.in_stream)
+
 #    def test_winrm_connection_module(self):
 #        self.assertIsInstance(WinRmConnection(), WinRmConnection)