From 7cf2edc3a8a5ed7e8dd3044be3ea9e2bca3eda96 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Wed, 28 Feb 2018 00:07:11 +0545 Subject: [PATCH] ansible: Support many more common playbook variables. --- ansible_mitogen/connection/mitogen.py | 36 ++++++++++++++++++++++++--- ansible_mitogen/mixins.py | 8 ++++++ ansible_mitogen/strategy/mitogen.py | 7 +++--- docs/ansible.rst | 29 +++++++++++++-------- docs/api.rst | 6 ++++- mitogen/parent.py | 11 +++++--- 6 files changed, 76 insertions(+), 21 deletions(-) diff --git a/ansible_mitogen/connection/mitogen.py b/ansible_mitogen/connection/mitogen.py index f624223d..79f286b7 100644 --- a/ansible_mitogen/connection/mitogen.py +++ b/ansible_mitogen/connection/mitogen.py @@ -65,16 +65,44 @@ class Connection(ansible.plugins.connection.ConnectionBase): #: hard-codes 'local' and 'ssh' as the only allowable connection types. transport = None + #: Set to 'ansible_python_interpreter' by on_action_run(). + python_path = None + + #: Set to 'ansible_sudo_exe' by on_action_run(). + sudo_path = None + + #: Set to 'ansible_ssh_timeout' by on_action_run(). + connect_timeout = None + def __init__(self, play_context, new_stdin, original_transport): assert 'MITOGEN_LISTENER_PATH' in os.environ, ( 'The "mitogen" connection plug-in may only be instantiated ' - 'by the "mitogen" strategy plugin.' + 'by the "mitogen" strategy plug-in.' ) self.original_transport = original_transport self.transport = original_transport super(Connection, self).__init__(play_context, new_stdin) + def on_action_run(self, task_vars): + """ + Invoked by ActionModuleMixin to indicate a new task is about to start + executing. We use the opportunity to grab relevant bits from the + task-specific data. + """ + self.connect_timeout = task_vars.get( + 'ansible_ssh_timeout', + None + ) + self.python_path = task_vars.get( + 'ansible_python_interpreter', + '/usr/bin/python' + ) + self.sudo_path = task_vars.get( + 'ansible_sudo_exe', + 'sudo' + ) + @property def connected(self): return self.router is not None @@ -102,8 +130,9 @@ class Connection(ansible.plugins.connection.ConnectionBase): 'username': self._play_context.remote_user, 'password': self._play_context.password, 'port': self._play_context.port, - 'python_path': '/usr/bin/python', + 'python_path': self.python_path, 'ssh_path': self._play_context.ssh_executable, + 'connect_timeout': self.connect_timeout, }) ) @@ -120,7 +149,8 @@ class Connection(ansible.plugins.connection.ConnectionBase): 'method': 'sudo', 'username': self._play_context.become_user, 'password': self._play_context.password, - 'python_path': python_path or '/usr/bin/python', + 'python_path': python_path or self.python_path, + 'sudo_path': self.sudo_path, 'via': via, })) diff --git a/ansible_mitogen/mixins.py b/ansible_mitogen/mixins.py index f4a0468f..9ac66a36 100644 --- a/ansible_mitogen/mixins.py +++ b/ansible_mitogen/mixins.py @@ -68,6 +68,14 @@ def get_command_module_name(module_name): class ActionModuleMixin(ansible.plugins.action.ActionBase): + def run(self, tmp=None, task_vars=None): + """ + Override run() to notify the Connection of task-specific data, so it + has a chance to know e.g. the Python interpreter in use. + """ + self._connection.on_action_run(task_vars) + return super(ActionModuleMixin, self).run(tmp, task_vars) + def call(self, func, *args, **kwargs): return self._connection.call(func, *args, **kwargs) diff --git a/ansible_mitogen/strategy/mitogen.py b/ansible_mitogen/strategy/mitogen.py index 0260fdc1..90bab41e 100644 --- a/ansible_mitogen/strategy/mitogen.py +++ b/ansible_mitogen/strategy/mitogen.py @@ -194,8 +194,7 @@ class StrategyModule(ansible.plugins.strategy.linear.StrategyModule): """ def _setup_master(self): """ - Construct a Router, Broker, mitogen.unix listener thread, and thread - serving connection requests from worker processes. + Construct a Router, Broker, and mitogen.unix listener """ self.router = mitogen.master.Router() self.router.responder.whitelist_prefix('ansible') @@ -205,7 +204,8 @@ class StrategyModule(ansible.plugins.strategy.linear.StrategyModule): def _setup_services(self): """ - Construct a ContextService and a thread to service requests for it. + Construct a ContextService and a thread to service requests for it + arriving from worker processes. """ self.service = ContextService(self.router) self.service_thread = threading.Thread(target=self.service.run) @@ -262,4 +262,3 @@ class StrategyModule(ansible.plugins.strategy.linear.StrategyModule): return self._run_with_master(iterator, play_context, result) finally: self._remove_wrappers() - self._setup_master() diff --git a/docs/ansible.rst b/docs/ansible.rst index a92044fc..69d9d221 100644 --- a/docs/ansible.rst +++ b/docs/ansible.rst @@ -77,13 +77,14 @@ High Risk file, the host machine could easily exhaust available RAM. This will be fixed soon as it's likely to be tickled by common playbook use cases. +* Situations may exist where the playbook's execution conditions are not + respected, however ``delegate_to``, ``connection: local``, ``become``, + ``become_user``, and ``local_action`` have all been tested. + + Medium Risk ~~~~~~~~~~~ -* The remote interpreter is temporarily hard-wired to ``/usr/bin/python``, - matching Ansible's default. The ``ansible_python_interpreter`` variable is - ignored. - * In some cases ``remote_tmp`` may not be respected. * Interaction with modules employing special action plugins is minimally @@ -194,18 +195,26 @@ SSH Variables This list will grow as more missing pieces are discovered. -* remote_addr -* remote_user -* ssh_port -* ssh_path +* ansible_python_interpreter +* ansible_ssh_timeout +* ansible_host, ansible_ssh_host +* ansible_user, ansible_ssh_user +* ansible_port, ssh_port +* ansible_ssh_executable, ssh_executable * password (default: assume passwordless) Sudo Variables -------------- -* username (default: root) -* password (default: assume passwordless) +* ansible_python_interpreter +* ansible_sudo_exe, ansible_become_exe +* ansible_sudo_user, ansible_become_user (default: root) +* ansible_sudo_pass, ansible_become_pass (default: assume passwordless) + +Unsupported: + +* sudo_flags Debugging diff --git a/docs/api.rst b/docs/api.rst index 993ae461..278c7749 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -503,7 +503,7 @@ Router Class **Context Factories** - .. method:: local (remote_name=None, python_path=None, debug=False, profiling=False, via=None) + .. method:: local (remote_name=None, python_path=None, debug=False, connect_timeout=None, profiling=False, via=None) Arrange for a context to be constructed on the local machine, as an immediate subprocess of the current process. The associated stream @@ -525,6 +525,10 @@ Router Class :py:meth:`enable_debug` has been called, but may be used selectively otherwise. + :param float connect_timeout: + Fractional seconds to wait for the subprocess to indicate it is + healthy. Defaults to 30 seconds. + :param bool profiling: If ``True``, arrange for profiling (:py:data:`profiling`) to be enabled in the new context. Automatically ``True`` when diff --git a/mitogen/parent.py b/mitogen/parent.py index 51e225d5..2d0a5f1c 100644 --- a/mitogen/parent.py +++ b/mitogen/parent.py @@ -269,6 +269,9 @@ class Stream(mitogen.core.Stream): #: The path to the remote Python interpreter. python_path = 'python2.7' + #: Maximum time to wait for a connection attempt. + connect_timeout = 30.0 + #: True to cause context to write verbose /tmp/mitogen..log. debug = False @@ -280,13 +283,14 @@ class Stream(mitogen.core.Stream): self.sent_modules = set(['mitogen', 'mitogen.core']) def construct(self, remote_name=None, python_path=None, debug=False, - profiling=False, **kwargs): + connect_timeout=None, profiling=False, **kwargs): """Get the named context running on the local machine, creating it if it does not exist.""" super(Stream, self).construct(**kwargs) if python_path: self.python_path = python_path - + if connect_timeout: + self.connect_timeout = connect_timeout if remote_name is None: remote_name = '%s@%s:%d' remote_name %= (getpass.getuser(), socket.gethostname(), os.getpid()) @@ -381,7 +385,8 @@ class Stream(mitogen.core.Stream): discard_until(self.receive_side.fd, 'EC1\n', time.time() + 10.0) def _connect_bootstrap(self): - discard_until(self.receive_side.fd, 'EC0\n', time.time() + 10.0) + deadline = time.time() + self.connect_timeout + discard_until(self.receive_side.fd, 'EC0\n', deadline) self._ec0_received()