From a4785c2691290df6047da5294632a8c428e67476 Mon Sep 17 00:00:00 2001 From: Adrian Likins Date: Mon, 22 Aug 2016 10:39:38 -0400 Subject: [PATCH] Fix docker connection plugin version tests and py2.6 compat (#16841) * Rm py2.7+ code in docker connection plugin The docker connection plugin was using subprocess.check_output which only exists in python 2.7 and later. Connection plugins need to support python2.6 so this replaces it with Popen/communicate() * Handle docker ver errors in docker connection Add unit tests for DockerConnection Fixes #16971 --- lib/ansible/plugins/connection/docker.py | 53 ++++++++++++------- .../plugins/connections/test_connection.py | 21 ++++++++ 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/lib/ansible/plugins/connection/docker.py b/lib/ansible/plugins/connection/docker.py index 62404fb43c6..7db04cba3e1 100644 --- a/lib/ansible/plugins/connection/docker.py +++ b/lib/ansible/plugins/connection/docker.py @@ -104,28 +104,43 @@ class Connection(ConnectionBase): def _sanitize_version(version): return re.sub('[^0-9a-zA-Z\.]', '', version) + 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() + + 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_args += self._play_context.docker_extra_args.split(' ') + + new_version_subcommand = ['version', '--format', "'{{.Server.Version}}'"] + + 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 + def _get_docker_version(self): - cmd = [self.docker_cmd] + 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]) - if self._play_context.docker_extra_args: - cmd += self._play_context.docker_extra_args.split(' ') - - cmd += ['version'] - - cmd_output = subprocess.check_output(cmd) - - for line in cmd_output.split('\n'): - if line.startswith('Server version:'): # old docker versions - return self._sanitize_version(line.split()[2]) - - # no result yet, must be newer Docker version - new_docker_cmd = [ - self.docker_cmd, - 'version', '--format', "'{{.Server.Version}}'" - ] - - 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) diff --git a/test/units/plugins/connections/test_connection.py b/test/units/plugins/connections/test_connection.py index 00d611c63c1..0e7f4da0a08 100644 --- a/test/units/plugins/connections/test_connection.py +++ b/test/units/plugins/connections/test_connection.py @@ -21,7 +21,10 @@ __metaclass__ = type 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.lxc import Connection as LxcConnection 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 @@ class TestConnectionBaseClass(unittest.TestCase): 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)