From 7fd88868a6c8939f1b37336b3b20e2c124ddac3a Mon Sep 17 00:00:00 2001 From: David Wilson Date: Thu, 5 Apr 2018 20:14:14 +0100 Subject: [PATCH] ansible: raise AnsibleConnectionFailure on connection failure; closes #183 Before: $ ANSIBLE_STRATEGY=mitogen ansible -i derp, derp -m setup An exception occurred during task execution. To see the full traceback, use -vvv. The error was: (''.join(bits)[-300:],) derp | FAILED! => { "msg": "Unexpected failure during module execution.", "stdout": "" } After: $ ANSIBLE_STRATEGY=mitogen ansible -i derp, derp -m setup derp | UNREACHABLE! => { "changed": false, "msg": "EOF on stream; last 300 bytes received: 'ssh: Could not resolve hostname derp: nodename nor servname provided, or not known\\r\\n'", "unreachable": true } --- ansible_mitogen/connection.py | 121 +++++++++++++++++----------------- ansible_mitogen/services.py | 16 ++++- 2 files changed, 75 insertions(+), 62 deletions(-) diff --git a/ansible_mitogen/connection.py b/ansible_mitogen/connection.py index a538f743..db182930 100644 --- a/ansible_mitogen/connection.py +++ b/ansible_mitogen/connection.py @@ -37,7 +37,7 @@ import ansible.errors import ansible.plugins.connection import mitogen.unix -from mitogen.utils import cast +import mitogen.utils import ansible_mitogen.helpers import ansible_mitogen.process @@ -137,59 +137,63 @@ class Connection(ansible.plugins.connection.ConnectionBase): def connected(self): return self.broker is not None + def _wrap_connect(self, args): + dct = mitogen.service.call( + context=self.parent, + handle=ContextService.handle, + obj=mitogen.utils.cast(args), + ) + + if dct['msg']: + raise ansible.errors.AnsibleConnectionFailure(dct['msg']) + + return dct['context'], dct['home_dir'] + def _connect_local(self): """ Fetch a reference to the local() Context from ContextService in the master process. """ - return mitogen.service.call(self.parent, ContextService.handle, cast({ + return self._wrap_connect({ 'method': 'local', 'python_path': self.python_path, - })) + }) def _connect_ssh(self): """ Fetch a reference to an SSH Context matching the play context from ContextService in the master process. """ - return mitogen.service.call( - self.parent, - ContextService.handle, - cast({ - 'method': 'ssh', - 'check_host_keys': False, # TODO - 'hostname': self._play_context.remote_addr, - 'discriminator': self.mitogen_ssh_discriminator, - 'username': self._play_context.remote_user, - 'password': self._play_context.password, - 'port': self._play_context.port, - 'python_path': self.python_path, - 'identity_file': self._play_context.private_key_file, - 'ssh_path': self._play_context.ssh_executable, - 'connect_timeout': self.ansible_ssh_timeout, - 'ssh_args': [ - term - for s in ( - getattr(self._play_context, 'ssh_args', ''), - getattr(self._play_context, 'ssh_common_args', ''), - getattr(self._play_context, 'ssh_extra_args', '') - ) - for term in shlex.split(s or '') - ] - }) - ) + return self._wrap_connect({ + 'method': 'ssh', + 'check_host_keys': False, # TODO + 'hostname': self._play_context.remote_addr, + 'discriminator': self.mitogen_ssh_discriminator, + 'username': self._play_context.remote_user, + 'password': self._play_context.password, + 'port': self._play_context.port, + 'python_path': self.python_path, + 'identity_file': self._play_context.private_key_file, + 'ssh_path': self._play_context.ssh_executable, + 'connect_timeout': self.ansible_ssh_timeout, + 'ssh_args': [ + term + for s in ( + getattr(self._play_context, 'ssh_args', ''), + getattr(self._play_context, 'ssh_common_args', ''), + getattr(self._play_context, 'ssh_extra_args', '') + ) + for term in shlex.split(s or '') + ] + }) def _connect_docker(self): - return mitogen.service.call( - self.parent, - ContextService.handle, - cast({ - 'method': 'docker', - 'container': self._play_context.remote_addr, - 'python_path': self.python_path, - 'connect_timeout': self._play_context.timeout, - }) - ) + return self._wrap_connect({ + 'method': 'docker', + 'container': self._play_context.remote_addr, + 'python_path': self.python_path, + 'connect_timeout': self._play_context.timeout, + }) def _connect_sudo(self, via=None, python_path=None): """ @@ -200,23 +204,19 @@ class Connection(ansible.plugins.connection.ConnectionBase): Parent Context of the sudo Context. For Ansible, this should always be a Context returned by _connect_ssh(). """ - return mitogen.service.call( - self.parent, - ContextService.handle, - cast({ - 'method': 'sudo', - 'username': self._play_context.become_user, - 'password': self._play_context.become_pass, - 'python_path': python_path or self.python_path, - 'sudo_path': self.sudo_path, - 'connect_timeout': self._play_context.timeout, - 'via': via, - 'sudo_args': shlex.split( - self._play_context.sudo_flags or - self._play_context.become_flags or '' - ), - }) - ) + return self._wrap_connect({ + 'method': 'sudo', + 'username': self._play_context.become_user, + 'password': self._play_context.become_pass, + 'python_path': python_path or self.python_path, + 'sudo_path': self.sudo_path, + 'connect_timeout': self._play_context.timeout, + 'via': via, + 'sudo_args': shlex.split( + self._play_context.sudo_flags or + self._play_context.become_flags or '' + ), + }) def _connect(self): """ @@ -318,8 +318,8 @@ class Connection(ansible.plugins.connection.ConnectionBase): emulate_tty = (not in_data and sudoable) rc, stdout, stderr = self.call( ansible_mitogen.helpers.exec_command, - cmd=cast(cmd), - in_data=cast(in_data), + cmd=mitogen.utils.cast(cmd), + in_data=mitogen.utils.cast(in_data), chdir=mitogen_chdir, emulate_tty=emulate_tty, ) @@ -341,7 +341,7 @@ class Connection(ansible.plugins.connection.ConnectionBase): Local filesystem path to write. """ output = self.call(ansible_mitogen.helpers.read_path, - cast(in_path)) + mitogen.utils.cast(in_path)) ansible_mitogen.helpers.write_path(out_path, output) def put_data(self, out_path, data): @@ -355,7 +355,8 @@ class Connection(ansible.plugins.connection.ConnectionBase): Remote filesystem path to write. """ self.call(ansible_mitogen.helpers.write_path, - cast(out_path), cast(data)) + mitogen.utils.cast(out_path), + mitogen.utils.cast(data)) def put_file(self, in_path, out_path): """ diff --git a/ansible_mitogen/services.py b/ansible_mitogen/services.py index 7bc59135..18782f20 100644 --- a/ansible_mitogen/services.py +++ b/ansible_mitogen/services.py @@ -81,9 +81,21 @@ class ContextService(mitogen.service.DeduplicatingService): def get_response(self, args): args.pop('discriminator', None) method = getattr(self.router, args.pop('method')) - context = method(**args) + try: + context = method(**args) + except mitogen.core.StreamError as e: + return { + 'context': None, + 'home_dir': None, + 'msg': str(e), + } + home_dir = context.call(os.path.expanduser, '~') - return context, home_dir + return { + 'context': context, + 'home_dir': home_dir, + 'msg': None, + } class FileService(mitogen.service.Service):