diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg
index c40f729df3d713064ad31d62b6e3ba0619246d53..827f17f5576bde9fb2df95819785946c04c872f0 100644
--- a/ansible/ansible.cfg
+++ b/ansible/ansible.cfg
@@ -3,5 +3,5 @@ callback_whitelist = profile_tasks, timer
 inventory = inventory.yml
 nocows = 1
 stdout_callback = yaml
-strategy_plugins = plugins/mitogen-0.2.7/ansible_mitogen/plugins/strategy
+strategy_plugins = plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/strategy
 strategy = mitogen_linear
diff --git a/ansible/bootstrap.yml b/ansible/bootstrap.yml
index 134b6b79c395d1a015ad675983a820c0f0b9a130..44809af9d59394fdbac24065072ae467b09e5e88 100644
--- a/ansible/bootstrap.yml
+++ b/ansible/bootstrap.yml
@@ -6,10 +6,9 @@
     - name: Require minimal ansible version
       assert:
         that:
-          - "ansible_version.full is version_compare('2.7', '>=')"
-          - "ansible_version.full is version_compare('2.8', '<')"
+          - "ansible_version.full is version_compare('2.8', '>=')"
         msg: >
-          "Please use Ansible 2.7.x to bootstrap your OAS cluster."
+          "Please use Ansible 2.8.x to bootstrap your OAS cluster."
 
     - name: Release name must start with lower case
       assert:
diff --git a/ansible/plugins/mitogen-0.2.7/LICENSE b/ansible/plugins/mitogen-0.2.8-pre/LICENSE
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/LICENSE
rename to ansible/plugins/mitogen-0.2.8-pre/LICENSE
diff --git a/ansible/plugins/mitogen-0.2.7/README.md b/ansible/plugins/mitogen-0.2.8-pre/README.md
similarity index 88%
rename from ansible/plugins/mitogen-0.2.7/README.md
rename to ansible/plugins/mitogen-0.2.8-pre/README.md
index 5ef2447f5308e242f4a7bb013f852cf79f8f9b96..da93a80b5b00f537fe36dd2eecd33ef79ef62862 100644
--- a/ansible/plugins/mitogen-0.2.7/README.md
+++ b/ansible/plugins/mitogen-0.2.8-pre/README.md
@@ -2,7 +2,7 @@
 # Mitogen
 
 <!-- [![Build Status](https://travis-ci.org/dw/mitogen.png?branch=master)](https://travis-ci.org/dw/mitogen}) -->
-<a href="https://mitogen.readthedocs.io/">Please see the documentation</a>.
+<a href="https://mitogen.networkgenomics.com/">Please see the documentation</a>.
 
 ![](https://i.imgur.com/eBM6LhJ.gif)
 
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/__init__.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/__init__.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/__init__.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/__init__.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/affinity.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/affinity.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/affinity.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/affinity.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/compat/__init__.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/compat/__init__.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/compat/__init__.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/compat/__init__.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/compat/simplejson/__init__.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/compat/simplejson/__init__.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/compat/simplejson/__init__.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/compat/simplejson/__init__.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/compat/simplejson/decoder.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/compat/simplejson/decoder.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/compat/simplejson/decoder.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/compat/simplejson/decoder.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/compat/simplejson/encoder.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/compat/simplejson/encoder.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/compat/simplejson/encoder.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/compat/simplejson/encoder.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/compat/simplejson/scanner.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/compat/simplejson/scanner.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/compat/simplejson/scanner.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/compat/simplejson/scanner.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/connection.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/connection.py
similarity index 97%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/connection.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/connection.py
index b5f28d342e19695f4c20a0a78adb9734a5aecfee..42fa2ef860b275d0735c48e9aced402f97304be8 100644
--- a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/connection.py
+++ b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/connection.py
@@ -145,9 +145,29 @@ def _connect_ssh(spec):
             'ssh_args': spec.ssh_args(),
             'ssh_debug_level': spec.mitogen_ssh_debug_level(),
             'remote_name': get_remote_name(spec),
+            'keepalive_count': (
+                spec.mitogen_ssh_keepalive_count() or 10
+            ),
+            'keepalive_interval': (
+                spec.mitogen_ssh_keepalive_interval() or 30
+            ),
         }
     }
 
+def _connect_buildah(spec):
+    """
+    Return ContextService arguments for a Buildah connection.
+    """
+    return {
+        'method': 'buildah',
+        'kwargs': {
+            'username': spec.remote_user(),
+            'container': spec.remote_addr(),
+            'python_path': spec.python_path(),
+            'connect_timeout': spec.ansible_ssh_timeout() or spec.timeout(),
+            'remote_name': get_remote_name(spec),
+        }
+    }
 
 def _connect_docker(spec):
     """
@@ -356,7 +376,7 @@ def _connect_mitogen_doas(spec):
             'username': spec.remote_user(),
             'password': spec.password(),
             'python_path': spec.python_path(),
-            'doas_path': spec.become_exe(),
+            'doas_path': spec.ansible_doas_exe(),
             'connect_timeout': spec.timeout(),
             'remote_name': get_remote_name(spec),
         }
@@ -367,6 +387,7 @@ def _connect_mitogen_doas(spec):
 #: generating ContextService keyword arguments matching a connection
 #: specification.
 CONNECTION_METHOD = {
+    'buildah': _connect_buildah,
     'docker': _connect_docker,
     'kubectl': _connect_kubectl,
     'jail': _connect_jail,
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/loaders.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/loaders.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/loaders.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/loaders.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/logging.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/logging.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/logging.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/logging.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/mixins.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/mixins.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/mixins.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/mixins.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/module_finder.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/module_finder.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/module_finder.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/module_finder.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/parsing.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/parsing.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/parsing.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/parsing.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/planner.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/planner.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/planner.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/planner.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/__init__.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/__init__.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/__init__.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/__init__.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/action/__init__.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/action/__init__.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/action/__init__.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/action/__init__.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/action/mitogen_get_stack.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/action/mitogen_get_stack.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/action/mitogen_get_stack.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/action/mitogen_get_stack.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/__init__.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/__init__.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/__init__.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/__init__.py
diff --git a/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_buildah.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_buildah.py
new file mode 100644
index 0000000000000000000000000000000000000000..017214b2469c497cf8a1e99016af1dbde41917eb
--- /dev/null
+++ b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_buildah.py
@@ -0,0 +1,44 @@
+# Copyright 2019, David Wilson
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors
+# may be used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import absolute_import
+import os.path
+import sys
+
+try:
+    import ansible_mitogen
+except ImportError:
+    base_dir = os.path.dirname(__file__)
+    sys.path.insert(0, os.path.abspath(os.path.join(base_dir, '../../..')))
+    del base_dir
+
+import ansible_mitogen.connection
+
+
+class Connection(ansible_mitogen.connection.Connection):
+    transport = 'buildah'
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_doas.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_doas.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_doas.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_doas.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_docker.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_docker.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_docker.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_docker.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_jail.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_jail.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_jail.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_jail.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_kubectl.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_kubectl.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_kubectl.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_kubectl.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_local.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_local.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_local.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_local.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_lxc.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_lxc.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_lxc.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_lxc.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_lxd.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_lxd.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_lxd.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_lxd.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_machinectl.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_machinectl.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_machinectl.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_machinectl.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_setns.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_setns.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_setns.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_setns.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_ssh.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_ssh.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_ssh.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_ssh.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_su.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_su.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_su.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_su.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_sudo.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_sudo.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/connection/mitogen_sudo.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/connection/mitogen_sudo.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/strategy/__init__.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/strategy/__init__.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/strategy/__init__.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/strategy/__init__.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/strategy/mitogen.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/strategy/mitogen.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen_free.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/strategy/mitogen_free.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen_free.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/strategy/mitogen_free.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen_host_pinned.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/strategy/mitogen_host_pinned.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen_host_pinned.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/strategy/mitogen_host_pinned.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen_linear.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/strategy/mitogen_linear.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/plugins/strategy/mitogen_linear.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/plugins/strategy/mitogen_linear.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/process.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/process.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/process.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/process.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/runner.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/runner.py
similarity index 92%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/runner.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/runner.py
index 30c36be75ed777865a50672a6a6e5a5e1767d105..843ffe19a3c9a82c4bd851d73a20b45a3c45888d 100644
--- a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/runner.py
+++ b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/runner.py
@@ -112,6 +112,45 @@ else:
                 for token in shlex.split(str(s), comments=comments)]
 
 
+class TempFileWatcher(object):
+    """
+    Since Ansible 2.7.0, lineinfile leaks file descriptors returned by
+    :func:`tempfile.mkstemp` (ansible/ansible#57327). Handle this and all
+    similar cases by recording descriptors produced by mkstemp during module
+    execution, and cleaning up any leaked descriptors on completion.
+    """
+    def __init__(self):
+        self._real_mkstemp = tempfile.mkstemp
+        # (fd, st.st_dev, st.st_ino)
+        self._fd_dev_inode = []
+        tempfile.mkstemp = self._wrap_mkstemp
+
+    def _wrap_mkstemp(self, *args, **kwargs):
+        fd, path = self._real_mkstemp(*args, **kwargs)
+        st = os.fstat(fd)
+        self._fd_dev_inode.append((fd, st.st_dev, st.st_ino))
+        return fd, path
+
+    def revert(self):
+        tempfile.mkstemp = self._real_mkstemp
+        for tup in self._fd_dev_inode:
+            self._revert_one(*tup)
+
+    def _revert_one(self, fd, st_dev, st_ino):
+        try:
+            st = os.fstat(fd)
+        except OSError:
+            # FD no longer exists.
+            return
+
+        if not (st.st_dev == st_dev and st.st_ino == st_ino):
+            # FD reused.
+            return
+
+        LOG.info("a tempfile.mkstemp() FD was leaked during the last task")
+        os.close(fd)
+
+
 class EnvironmentFileWatcher(object):
     """
     Usually Ansible edits to /etc/environment and ~/.pam_environment are
@@ -344,11 +383,22 @@ class Runner(object):
             env.update(self.env)
         self._env = TemporaryEnvironment(env)
 
+    def _revert_cwd(self):
+        """
+        #591: make a best-effort attempt to return to :attr:`good_temp_dir`.
+        """
+        try:
+            os.chdir(self.good_temp_dir)
+        except OSError:
+            LOG.debug('%r: could not restore CWD to %r',
+                      self, self.good_temp_dir)
+
     def revert(self):
         """
         Revert any changes made to the process after running a module. The base
         implementation simply restores the original environment.
         """
+        self._revert_cwd()
         self._env.revert()
         self.revert_temp_dir()
 
@@ -760,7 +810,21 @@ class NewStyleRunner(ScriptRunner):
         for fullname, _, _ in self.module_map['custom']:
             mitogen.core.import_module(fullname)
         for fullname in self.module_map['builtin']:
-            mitogen.core.import_module(fullname)
+            try:
+                mitogen.core.import_module(fullname)
+            except ImportError:
+                # #590: Ansible 2.8 module_utils.distro is a package that
+                # replaces itself in sys.modules with a non-package during
+                # import. Prior to replacement, it is a real package containing
+                # a '_distro' submodule which is used on 2.x. Given a 2.x
+                # controller and 3.x target, the import hook never needs to run
+                # again before this replacement occurs, and 'distro' is
+                # replaced with a module from the stdlib. In this case as this
+                # loop progresses to the next entry and attempts to preload
+                # 'distro._distro', the import mechanism will fail. So here we
+                # silently ignore any failure for it.
+                if fullname != 'ansible.module_utils.distro._distro':
+                    raise
 
     def _setup_excepthook(self):
         """
@@ -778,6 +842,7 @@ class NewStyleRunner(ScriptRunner):
         # module, but this has never been a bug report. Instead act like an
         # interpreter that had its script piped on stdin.
         self._argv = TemporaryArgv([''])
+        self._temp_watcher = TempFileWatcher()
         self._importer = ModuleUtilsImporter(
             context=self.service_context,
             module_utils=self.module_map['custom'],
@@ -793,6 +858,7 @@ class NewStyleRunner(ScriptRunner):
 
     def revert(self):
         self.atexit_wrapper.revert()
+        self._temp_watcher.revert()
         self._argv.revert()
         self._stdio.revert()
         self._revert_excepthook()
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/services.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/services.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/services.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/services.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/strategy.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/strategy.py
similarity index 93%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/strategy.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/strategy.py
index b9211fcc01793d220c8d5981b4e35937130e7241..01dff285401dcbd07c1daffffc8ffec77d4e8556 100644
--- a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/strategy.py
+++ b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/strategy.py
@@ -40,9 +40,15 @@ import ansible_mitogen.process
 import ansible
 import ansible.executor.process.worker
 
+try:
+    # 2.8+ has a standardized "unset" object.
+    from ansible.utils.sentinel import Sentinel
+except ImportError:
+    Sentinel = None
+
 
 ANSIBLE_VERSION_MIN = '2.3'
-ANSIBLE_VERSION_MAX = '2.7'
+ANSIBLE_VERSION_MAX = '2.8'
 NEW_VERSION_MSG = (
     "Your Ansible version (%s) is too recent. The most recent version\n"
     "supported by Mitogen for Ansible is %s.x. Please check the Mitogen\n"
@@ -115,7 +121,11 @@ def wrap_action_loader__get(name, *args, **kwargs):
     This is used instead of static subclassing as it generalizes to third party
     action modules outside the Ansible tree.
     """
-    klass = action_loader__get(name, class_only=True)
+    get_kwargs = {'class_only': True}
+    if ansible.__version__ >= '2.8':
+        get_kwargs['collection_list'] = kwargs.pop('collection_list', None)
+
+    klass = action_loader__get(name, **get_kwargs)
     if klass:
         bases = (ansible_mitogen.mixins.ActionModuleMixin, klass)
         adorned_klass = type(str(name), bases, {})
@@ -129,8 +139,8 @@ def wrap_connection_loader__get(name, *args, **kwargs):
     While the strategy is active, rewrite connection_loader.get() calls for
     some transports into requests for a compatible Mitogen transport.
     """
-    if name in ('docker', 'kubectl', 'jail', 'local', 'lxc',
-                'lxd', 'machinectl', 'setns', 'ssh'):
+    if name in ('buildah', 'docker', 'kubectl', 'jail', 'local',
+                'lxc', 'lxd', 'machinectl', 'setns', 'ssh'):
         name = 'mitogen_' + name
     return connection_loader__get(name, *args, **kwargs)
 
@@ -261,14 +271,17 @@ class StrategyMixin(object):
             name=task.action,
             mod_type='',
         )
-        ansible_mitogen.loaders.connection_loader.get(
-            name=play_context.connection,
-            class_only=True,
-        )
         ansible_mitogen.loaders.action_loader.get(
             name=task.action,
             class_only=True,
         )
+        if play_context.connection is not Sentinel:
+            # 2.8 appears to defer computing this until inside the worker.
+            # TODO: figure out where it has moved.
+            ansible_mitogen.loaders.connection_loader.get(
+                name=play_context.connection,
+                class_only=True,
+            )
 
         return super(StrategyMixin, self)._queue_task(
             host=host,
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/target.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/target.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/target.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/target.py
diff --git a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/transport_config.py b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/transport_config.py
similarity index 90%
rename from ansible/plugins/mitogen-0.2.7/ansible_mitogen/transport_config.py
rename to ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/transport_config.py
index ad1cab3ed65de782f96ffa64eca09ef2f6bd5462..aa4a16d0172848930e006bd4c612a0817cbbba79 100644
--- a/ansible/plugins/mitogen-0.2.7/ansible_mitogen/transport_config.py
+++ b/ansible/plugins/mitogen-0.2.8-pre/ansible_mitogen/transport_config.py
@@ -240,6 +240,12 @@ class Spec(with_metaclass(abc.ABCMeta, object)):
         undesirable in some circumstances.
         """
 
+    @abc.abstractmethod
+    def mitogen_buildah_path(self):
+        """
+        The path to the "buildah" program for the 'buildah' transport.
+        """
+
     @abc.abstractmethod
     def mitogen_docker_path(self):
         """
@@ -276,6 +282,18 @@ class Spec(with_metaclass(abc.ABCMeta, object)):
         The path to the "machinectl" program for the 'setns' transport.
         """
 
+    @abc.abstractmethod
+    def mitogen_ssh_keepalive_interval(self):
+        """
+        The SSH ServerAliveInterval.
+        """
+
+    @abc.abstractmethod
+    def mitogen_ssh_keepalive_count(self):
+        """
+        The SSH ServerAliveCount.
+        """
+
     @abc.abstractmethod
     def mitogen_ssh_debug_level(self):
         """
@@ -294,6 +312,12 @@ class Spec(with_metaclass(abc.ABCMeta, object)):
         Connection-specific arguments.
         """
 
+    @abc.abstractmethod
+    def ansible_doas_exe(self):
+        """
+        Value of "ansible_doas_exe" variable.
+        """
+
 
 class PlayContextSpec(Spec):
     """
@@ -372,7 +396,15 @@ class PlayContextSpec(Spec):
         ]
 
     def become_exe(self):
-        return self._play_context.become_exe
+        # In Ansible 2.8, PlayContext.become_exe always has a default value due
+        # to the new options mechanism. Previously it was only set if a value
+        # ("somewhere") had been specified for the task.
+        # For consistency in the tests, here we make older Ansibles behave like
+        # newer Ansibles.
+        exe = self._play_context.become_exe
+        if exe is None and self._play_context.become_method == 'sudo':
+            exe = 'sudo'
+        return exe
 
     def sudo_args(self):
         return [
@@ -380,8 +412,9 @@ class PlayContextSpec(Spec):
             for term in ansible.utils.shlex.shlex_split(
                 first_true((
                     self._play_context.become_flags,
-                    self._play_context.sudo_flags,
-                    # Ansible 2.3.
+                    # Ansible <=2.7.
+                    getattr(self._play_context, 'sudo_flags', ''),
+                    # Ansible <=2.3.
                     getattr(C, 'DEFAULT_BECOME_FLAGS', ''),
                     getattr(C, 'DEFAULT_SUDO_FLAGS', '')
                 ), default='')
@@ -397,6 +430,9 @@ class PlayContextSpec(Spec):
     def mitogen_mask_remote_name(self):
         return self._connection.get_task_var('mitogen_mask_remote_name')
 
+    def mitogen_buildah_path(self):
+        return self._connection.get_task_var('mitogen_buildah_path')
+
     def mitogen_docker_path(self):
         return self._connection.get_task_var('mitogen_docker_path')
 
@@ -412,6 +448,12 @@ class PlayContextSpec(Spec):
     def mitogen_lxc_info_path(self):
         return self._connection.get_task_var('mitogen_lxc_info_path')
 
+    def mitogen_ssh_keepalive_interval(self):
+        return self._connection.get_task_var('mitogen_ssh_keepalive_interval')
+
+    def mitogen_ssh_keepalive_count(self):
+        return self._connection.get_task_var('mitogen_ssh_keepalive_count')
+
     def mitogen_machinectl_path(self):
         return self._connection.get_task_var('mitogen_machinectl_path')
 
@@ -424,6 +466,12 @@ class PlayContextSpec(Spec):
     def extra_args(self):
         return self._connection.get_extra_args()
 
+    def ansible_doas_exe(self):
+        return (
+            self._connection.get_task_var('ansible_doas_exe') or
+            os.environ.get('ANSIBLE_DOAS_EXE')
+        )
+
 
 class MitogenViaSpec(Spec):
     """
@@ -608,6 +656,9 @@ class MitogenViaSpec(Spec):
     def mitogen_mask_remote_name(self):
         return self._host_vars.get('mitogen_mask_remote_name')
 
+    def mitogen_buildah_path(self):
+        return self._host_vars.get('mitogen_buildah_path')
+
     def mitogen_docker_path(self):
         return self._host_vars.get('mitogen_docker_path')
 
@@ -623,6 +674,12 @@ class MitogenViaSpec(Spec):
     def mitogen_lxc_info_path(self):
         return self._host_vars.get('mitogen_lxc_info_path')
 
+    def mitogen_ssh_keepalive_interval(self):
+        return self._host_vars.get('mitogen_ssh_keepalive_interval')
+
+    def mitogen_ssh_keepalive_count(self):
+        return self._host_vars.get('mitogen_ssh_keepalive_count')
+
     def mitogen_machinectl_path(self):
         return self._host_vars.get('mitogen_machinectl_path')
 
@@ -634,3 +691,9 @@ class MitogenViaSpec(Spec):
 
     def extra_args(self):
         return []  # TODO
+
+    def ansible_doas_exe(self):
+        return (
+            self._host_vars.get('ansible_doas_exe') or
+            os.environ.get('ANSIBLE_DOAS_EXE')
+        )
diff --git a/ansible/plugins/mitogen-0.2.7/dev_requirements.txt b/ansible/plugins/mitogen-0.2.8-pre/dev_requirements.txt
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/dev_requirements.txt
rename to ansible/plugins/mitogen-0.2.8-pre/dev_requirements.txt
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/__init__.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/__init__.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/__init__.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/__init__.py
diff --git a/ansible/plugins/mitogen-0.2.8-pre/mitogen/buildah.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/buildah.py
new file mode 100644
index 0000000000000000000000000000000000000000..eec415f3223dd3eced64dd9d10317522165a6125
--- /dev/null
+++ b/ansible/plugins/mitogen-0.2.8-pre/mitogen/buildah.py
@@ -0,0 +1,73 @@
+# Copyright 2019, David Wilson
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors
+# may be used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+# !mitogen: minify_safe
+
+import logging
+
+import mitogen.core
+import mitogen.parent
+
+
+LOG = logging.getLogger(__name__)
+
+
+class Stream(mitogen.parent.Stream):
+    child_is_immediate_subprocess = False
+
+    container = None
+    username = None
+    buildah_path = 'buildah'
+
+    # TODO: better way of capturing errors such as "No such container."
+    create_child_args = {
+        'merge_stdio': True
+    }
+
+    def construct(self, container=None,
+                  buildah_path=None, username=None,
+                  **kwargs):
+        assert container or image
+        super(Stream, self).construct(**kwargs)
+        if container:
+            self.container = container
+        if buildah_path:
+            self.buildah_path = buildah_path
+        if username:
+            self.username = username
+
+    def _get_name(self):
+        return u'buildah.' + self.container
+
+    def get_boot_command(self):
+        args = []
+        if self.username:
+            args += ['--user=' + self.username]
+        bits = [self.buildah_path, 'run'] + args + ['--', self.container]
+
+        return bits + super(Stream, self).get_boot_command()
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/compat/__init__.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/compat/__init__.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/compat/__init__.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/compat/__init__.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/compat/pkgutil.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/compat/pkgutil.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/compat/pkgutil.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/compat/pkgutil.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/compat/tokenize.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/compat/tokenize.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/compat/tokenize.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/compat/tokenize.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/core.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/core.py
similarity index 99%
rename from ansible/plugins/mitogen-0.2.7/mitogen/core.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/core.py
index ff77bba99c7868393b00bcf31e5777f361d322ce..ea83f9618e1a2b411e6ad285f0a9652de0234750 100644
--- a/ansible/plugins/mitogen-0.2.7/mitogen/core.py
+++ b/ansible/plugins/mitogen-0.2.8-pre/mitogen/core.py
@@ -1089,6 +1089,7 @@ class Importer(object):
     # The Mitogen package is handled specially, since the child context must
     # construct it manually during startup.
     MITOGEN_PKG_CONTENT = [
+        'buildah',
         'compat',
         'debug',
         'doas',
@@ -1355,7 +1356,10 @@ class Importer(object):
             exec(code, vars(mod))
         else:
             exec('exec code in vars(mod)')
-        return mod
+
+        # #590: if a module replaces itself in sys.modules during import, below
+        # is necessary. This matches PyImport_ExecCodeModuleEx()
+        return sys.modules.get(fullname, mod)
 
     def get_filename(self, fullname):
         if fullname in self._cache:
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/debug.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/debug.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/debug.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/debug.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/doas.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/doas.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/doas.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/doas.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/docker.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/docker.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/docker.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/docker.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/fakessh.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/fakessh.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/fakessh.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/fakessh.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/fork.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/fork.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/fork.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/fork.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/jail.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/jail.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/jail.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/jail.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/kubectl.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/kubectl.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/kubectl.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/kubectl.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/lxc.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/lxc.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/lxc.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/lxc.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/lxd.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/lxd.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/lxd.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/lxd.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/master.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/master.py
similarity index 87%
rename from ansible/plugins/mitogen-0.2.7/mitogen/master.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/master.py
index 1396f4e10fb523dee65fc8e62d4a03f774622606..fb4f505b5f413076f250627d0208dcaccc5b5d8d 100644
--- a/ansible/plugins/mitogen-0.2.7/mitogen/master.py
+++ b/ansible/plugins/mitogen-0.2.8-pre/mitogen/master.py
@@ -36,6 +36,7 @@ contexts.
 """
 
 import dis
+import errno
 import imp
 import inspect
 import itertools
@@ -45,11 +46,16 @@ import pkgutil
 import re
 import string
 import sys
-import time
 import threading
+import time
 import types
 import zlib
 
+try:
+    import sysconfig
+except ImportError:
+    sysconfig = None
+
 if not hasattr(pkgutil, 'find_loader'):
     # find_loader() was new in >=2.5, but the modern pkgutil.py syntax has
     # been kept intentionally 2.3 compatible so we can reuse it.
@@ -92,10 +98,16 @@ def _stdlib_paths():
         'real_prefix',  # virtualenv: only set inside a virtual environment.
         'base_prefix',  # venv: always set, equal to prefix if outside.
     ]
-    prefixes = (getattr(sys, a) for a in attr_candidates if hasattr(sys, a))
+    prefixes = (getattr(sys, a, None) for a in attr_candidates)
     version = 'python%s.%s' % sys.version_info[0:2]
-    return set(os.path.abspath(os.path.join(p, 'lib', version))
-               for p in prefixes)
+    s = set(os.path.abspath(os.path.join(p, 'lib', version))
+            for p in prefixes if p is not None)
+
+    # When running 'unit2 tests/module_finder_test.py' in a Py2 venv on Ubuntu
+    # 18.10, above is insufficient to catch the real directory.
+    if sysconfig is not None:
+        s.add(sysconfig.get_config_var('DESTLIB'))
+    return s
 
 
 def is_stdlib_name(modname):
@@ -142,6 +154,41 @@ def get_child_modules(path):
     return [to_text(name) for _, name, _ in it]
 
 
+def _looks_like_script(path):
+    """
+    Return :data:`True` if the (possibly extensionless) file at `path`
+    resembles a Python script. For now we simply verify the file contains
+    ASCII text.
+    """
+    try:
+        fp = open(path, 'rb')
+    except IOError:
+        e = sys.exc_info()[1]
+        if e.args[0] == errno.EISDIR:
+            return False
+        raise
+
+    try:
+        sample = fp.read(512).decode('latin-1')
+        return not set(sample).difference(string.printable)
+    finally:
+        fp.close()
+
+
+def _py_filename(path):
+    if not path:
+        return None
+
+    if path[-4:] in ('.pyc', '.pyo'):
+        path = path.rstrip('co')
+
+    if path.endswith('.py'):
+        return path
+
+    if os.path.exists(path) and _looks_like_script(path):
+        return path
+
+
 def _get_core_source():
     """
     Master version of parent.get_core_source().
@@ -368,56 +415,38 @@ class LogForwarder(object):
         return 'LogForwarder(%r)' % (self._router,)
 
 
-class ModuleFinder(object):
+class FinderMethod(object):
     """
-    Given the name of a loaded module, make a best-effort attempt at finding
-    related modules likely needed by a child context requesting the original
-    module.
+    Interface to a method for locating a Python module or package given its
+    name according to the running Python interpreter. You'd think this was a
+    simple task, right? Naive young fellow, welcome to the real world.
     """
-    def __init__(self):
-        #: Import machinery is expensive, keep :py:meth`:get_module_source`
-        #: results around.
-        self._found_cache = {}
-
-        #: Avoid repeated dependency scanning, which is expensive.
-        self._related_cache = {}
-
     def __repr__(self):
-        return 'ModuleFinder()'
+        return '%s()' % (type(self).__name__,)
 
-    def _looks_like_script(self, path):
-        """
-        Return :data:`True` if the (possibly extensionless) file at `path`
-        resembles a Python script. For now we simply verify the file contains
-        ASCII text.
+    def find(self, fullname):
         """
-        fp = open(path, 'rb')
-        try:
-            sample = fp.read(512).decode('latin-1')
-            return not set(sample).difference(string.printable)
-        finally:
-            fp.close()
+        Accept a canonical module name and return `(path, source, is_pkg)`
+        tuples, where:
 
-    def _py_filename(self, path):
-        if not path:
-            return None
-
-        if path[-4:] in ('.pyc', '.pyo'):
-            path = path.rstrip('co')
+        * `path`: Unicode string containing path to source file.
+        * `source`: Bytestring containing source file's content.
+        * `is_pkg`: :data:`True` if `fullname` is a package.
 
-        if path.endswith('.py'):
-            return path
+        :returns:
+            :data:`None` if not found, or tuple as described above.
+        """
+        raise NotImplementedError()
 
-        if os.path.exists(path) and self._looks_like_script(path):
-            return path
 
-    def _get_main_module_defective_python_3x(self, fullname):
-        """
-        Recent versions of Python 3.x introduced an incomplete notion of
-        importer specs, and in doing so created permanent asymmetry in the
-        :mod:`pkgutil` interface handling for the `__main__` module. Therefore
-        we must handle `__main__` specially.
-        """
+class DefectivePython3xMainMethod(FinderMethod):
+    """
+    Recent versions of Python 3.x introduced an incomplete notion of
+    importer specs, and in doing so created permanent asymmetry in the
+    :mod:`pkgutil` interface handling for the `__main__` module. Therefore
+    we must handle `__main__` specially.
+    """
+    def find(self, fullname):
         if fullname != '__main__':
             return None
 
@@ -426,7 +455,7 @@ class ModuleFinder(object):
             return None
 
         path = getattr(mod, '__file__', None)
-        if not (os.path.exists(path) and self._looks_like_script(path)):
+        if not (os.path.exists(path) and _looks_like_script(path)):
             return None
 
         fp = open(path, 'rb')
@@ -437,11 +466,13 @@ class ModuleFinder(object):
 
         return path, source, False
 
-    def _get_module_via_pkgutil(self, fullname):
-        """
-        Attempt to fetch source code via pkgutil. In an ideal world, this would
-        be the only required implementation of get_module().
-        """
+
+class PkgutilMethod(FinderMethod):
+    """
+    Attempt to fetch source code via pkgutil. In an ideal world, this would
+    be the only required implementation of get_module().
+    """
+    def find(self, fullname):
         try:
             # Pre-'import spec' this returned None, in Python3.6 it raises
             # ImportError.
@@ -458,7 +489,7 @@ class ModuleFinder(object):
             return
 
         try:
-            path = self._py_filename(loader.get_filename(fullname))
+            path = _py_filename(loader.get_filename(fullname))
             source = loader.get_source(fullname)
             is_pkg = loader.is_package(fullname)
         except (AttributeError, ImportError):
@@ -484,19 +515,27 @@ class ModuleFinder(object):
 
         return path, source, is_pkg
 
-    def _get_module_via_sys_modules(self, fullname):
-        """
-        Attempt to fetch source code via sys.modules. This is specifically to
-        support __main__, but it may catch a few more cases.
-        """
+
+class SysModulesMethod(FinderMethod):
+    """
+    Attempt to fetch source code via sys.modules. This is specifically to
+    support __main__, but it may catch a few more cases.
+    """
+    def find(self, fullname):
         module = sys.modules.get(fullname)
         LOG.debug('_get_module_via_sys_modules(%r) -> %r', fullname, module)
+        if getattr(module, '__name__', None) != fullname:
+            LOG.debug('sys.modules[%r].__name__ does not match %r, assuming '
+                      'this is a hacky module alias and ignoring it',
+                      fullname, fullname)
+            return
+
         if not isinstance(module, types.ModuleType):
             LOG.debug('sys.modules[%r] absent or not a regular module',
                       fullname)
             return
 
-        path = self._py_filename(getattr(module, '__file__', ''))
+        path = _py_filename(getattr(module, '__file__', ''))
         if not path:
             return
 
@@ -517,12 +556,19 @@ class ModuleFinder(object):
 
         return path, source, is_pkg
 
-    def _get_module_via_parent_enumeration(self, fullname):
-        """
-        Attempt to fetch source code by examining the module's (hopefully less
-        insane) parent package. Required for older versions of
-        ansible.compat.six and plumbum.colors.
-        """
+
+class ParentEnumerationMethod(FinderMethod):
+    """
+    Attempt to fetch source code by examining the module's (hopefully less
+    insane) parent package. Required for older versions of
+    ansible.compat.six and plumbum.colors, and Ansible 2.8
+    ansible.module_utils.distro.
+
+    For cases like module_utils.distro, this must handle cases where a package
+    transmuted itself into a totally unrelated module during import and vice
+    versa.
+    """
+    def find(self, fullname):
         if fullname not in sys.modules:
             # Don't attempt this unless a module really exists in sys.modules,
             # else we could return junk.
@@ -531,30 +577,68 @@ class ModuleFinder(object):
         pkgname, _, modname = str_rpartition(to_text(fullname), u'.')
         pkg = sys.modules.get(pkgname)
         if pkg is None or not hasattr(pkg, '__file__'):
+            LOG.debug('%r: %r is not a package or lacks __file__ attribute',
+                      self, pkgname)
             return
 
-        pkg_path = os.path.dirname(pkg.__file__)
+        pkg_path = [os.path.dirname(pkg.__file__)]
         try:
-            fp, path, ext = imp.find_module(modname, [pkg_path])
-            try:
-                path = self._py_filename(path)
-                if not path:
-                    fp.close()
-                    return
-
-                source = fp.read()
-            finally:
-                if fp:
-                    fp.close()
-
-            if isinstance(source, mitogen.core.UnicodeType):
-                # get_source() returns "string" according to PEP-302, which was
-                # reinterpreted for Python 3 to mean a Unicode string.
-                source = source.encode('utf-8')
-            return path, source, False
+            fp, path, (suffix, _, kind) = imp.find_module(modname, pkg_path)
         except ImportError:
             e = sys.exc_info()[1]
-            LOG.debug('imp.find_module(%r, %r) -> %s', modname, [pkg_path], e)
+            LOG.debug('%r: imp.find_module(%r, %r) -> %s',
+                      self, modname, [pkg_path], e)
+            return None
+
+        if kind == imp.PKG_DIRECTORY:
+            return self._found_package(fullname, path)
+        else:
+            return self._found_module(fullname, path, fp)
+
+    def _found_package(self, fullname, path):
+        path = os.path.join(path, '__init__.py')
+        LOG.debug('%r: %r is PKG_DIRECTORY: %r', self, fullname, path)
+        return self._found_module(
+            fullname=fullname,
+            path=path,
+            fp=open(path, 'rb'),
+            is_pkg=True,
+        )
+
+    def _found_module(self, fullname, path, fp, is_pkg=False):
+        try:
+            path = _py_filename(path)
+            if not path:
+                return
+
+            source = fp.read()
+        finally:
+            if fp:
+                fp.close()
+
+        if isinstance(source, mitogen.core.UnicodeType):
+            # get_source() returns "string" according to PEP-302, which was
+            # reinterpreted for Python 3 to mean a Unicode string.
+            source = source.encode('utf-8')
+        return path, source, is_pkg
+
+
+class ModuleFinder(object):
+    """
+    Given the name of a loaded module, make a best-effort attempt at finding
+    related modules likely needed by a child context requesting the original
+    module.
+    """
+    def __init__(self):
+        #: Import machinery is expensive, keep :py:meth`:get_module_source`
+        #: results around.
+        self._found_cache = {}
+
+        #: Avoid repeated dependency scanning, which is expensive.
+        self._related_cache = {}
+
+    def __repr__(self):
+        return 'ModuleFinder()'
 
     def add_source_override(self, fullname, path, source, is_pkg):
         """
@@ -576,10 +660,10 @@ class ModuleFinder(object):
         self._found_cache[fullname] = (path, source, is_pkg)
 
     get_module_methods = [
-        _get_main_module_defective_python_3x,
-        _get_module_via_pkgutil,
-        _get_module_via_sys_modules,
-        _get_module_via_parent_enumeration,
+        DefectivePython3xMainMethod(),
+        PkgutilMethod(),
+        SysModulesMethod(),
+        ParentEnumerationMethod(),
     ]
 
     def get_module_source(self, fullname):
@@ -595,7 +679,7 @@ class ModuleFinder(object):
             return tup
 
         for method in self.get_module_methods:
-            tup = method(self, fullname)
+            tup = method.find(fullname)
             if tup:
                 #LOG.debug('%r returned %r', method, tup)
                 break
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/minify.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/minify.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/minify.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/minify.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/os_fork.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/os_fork.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/os_fork.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/os_fork.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/parent.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/parent.py
similarity index 99%
rename from ansible/plugins/mitogen-0.2.7/mitogen/parent.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/parent.py
index 3d02bc43e99c97acdda345543d09acfaa2686c38..113fdc2e9d7ed6741f28cf606e79e066d051e883 100644
--- a/ansible/plugins/mitogen-0.2.7/mitogen/parent.py
+++ b/ansible/plugins/mitogen-0.2.8-pre/mitogen/parent.py
@@ -2163,6 +2163,9 @@ class Router(mitogen.core.Router):
             self._write_lock.release()
         return context
 
+    def buildah(self, **kwargs):
+        return self.connect(u'buildah', **kwargs)
+
     def doas(self, **kwargs):
         return self.connect(u'doas', **kwargs)
 
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/profiler.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/profiler.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/profiler.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/profiler.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/select.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/select.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/select.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/select.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/service.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/service.py
similarity index 99%
rename from ansible/plugins/mitogen-0.2.7/mitogen/service.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/service.py
index 302e81ab8ff3f727e73a2d1788e388093a4429b6..942ed4f7f4ef8885336c36ba6beac696b5378727 100644
--- a/ansible/plugins/mitogen-0.2.7/mitogen/service.py
+++ b/ansible/plugins/mitogen-0.2.8-pre/mitogen/service.py
@@ -625,7 +625,7 @@ class PushFileService(Service):
     """
     Push-based file service. Files are delivered and cached in RAM, sent
     recursively from parent to child. A child that requests a file via
-    :meth:`get` will block until it has ben delivered by a parent.
+    :meth:`get` will block until it has been delivered by a parent.
 
     This service will eventually be merged into FileService.
     """
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/setns.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/setns.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/setns.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/setns.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/ssh.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/ssh.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/ssh.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/ssh.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/su.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/su.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/su.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/su.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/sudo.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/sudo.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/sudo.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/sudo.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/unix.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/unix.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/unix.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/unix.py
diff --git a/ansible/plugins/mitogen-0.2.7/mitogen/utils.py b/ansible/plugins/mitogen-0.2.8-pre/mitogen/utils.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/mitogen/utils.py
rename to ansible/plugins/mitogen-0.2.8-pre/mitogen/utils.py
diff --git a/ansible/plugins/mitogen-0.2.7/preamble_size.py b/ansible/plugins/mitogen-0.2.8-pre/preamble_size.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/preamble_size.py
rename to ansible/plugins/mitogen-0.2.8-pre/preamble_size.py
diff --git a/ansible/plugins/mitogen-0.2.8-pre/scripts/debug-helpers.sh b/ansible/plugins/mitogen-0.2.8-pre/scripts/debug-helpers.sh
new file mode 100644
index 0000000000000000000000000000000000000000..7011c18c6757d8b9662d0e59df98c105a26ad997
--- /dev/null
+++ b/ansible/plugins/mitogen-0.2.8-pre/scripts/debug-helpers.sh
@@ -0,0 +1,39 @@
+#
+# Bash helpers for debugging.
+#
+
+# Tell Ansible to write PID files for the mux and top-level process to CWD.
+export MITOGEN_SAVE_PIDS=1
+
+
+# strace -ff -p $(muxpid)
+muxpid() {
+    cat .ansible-mux.pid
+}
+
+# gdb -p $(anspid)
+anspid() {
+    cat .ansible-controller.pid
+}
+
+# perf top -git $(muxtids)
+# perf top -git $(muxtids)
+muxtids() {
+    ls /proc/$(muxpid)/task | tr \\n ,
+}
+
+# perf top -git $(anstids)
+anstids() {
+    ls /proc/$(anspid)/task | tr \\n ,
+}
+
+# ttrace $(muxpid) [.. options ..]
+#   strace only threads of PID, not children
+ttrace() {
+    local pid=$1; shift;
+    local s=""
+    for i in $(ls /proc/$pid/task) ; do
+        s="-p $i $s"
+    done
+    strace $s "$@"
+}
diff --git a/ansible/plugins/mitogen-0.2.7/scripts/pogrep.py b/ansible/plugins/mitogen-0.2.8-pre/scripts/pogrep.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/scripts/pogrep.py
rename to ansible/plugins/mitogen-0.2.8-pre/scripts/pogrep.py
diff --git a/ansible/plugins/mitogen-0.2.7/setup.cfg b/ansible/plugins/mitogen-0.2.8-pre/setup.cfg
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/setup.cfg
rename to ansible/plugins/mitogen-0.2.8-pre/setup.cfg
diff --git a/ansible/plugins/mitogen-0.2.7/setup.py b/ansible/plugins/mitogen-0.2.8-pre/setup.py
similarity index 100%
rename from ansible/plugins/mitogen-0.2.7/setup.py
rename to ansible/plugins/mitogen-0.2.8-pre/setup.py
diff --git a/ansible/requirements.txt b/ansible/requirements.txt
index 75eb868d1989249d67174423a82e8313d10215f2..5b90d017c8db6f4a1489967d0d550722bfb9460b 100644
--- a/ansible/requirements.txt
+++ b/ansible/requirements.txt
@@ -1,3 +1,2 @@
 # ansible>=2.7 is needed for using the `k8s` resource
-# [mitogen currently doesn't work with ansible 2.8](https://github.com/dw/mitogen/issues/587)
-ansible>=2.7,<2.8
+ansible>=2.7
diff --git a/test/requirements.txt b/test/requirements.txt
index aa1bb55727dffacc73b3ecf95df51610dc1f94e2..206c8817228eba30d79527d565aaad0c779d18a2 100644
--- a/test/requirements.txt
+++ b/test/requirements.txt
@@ -1,10 +1,11 @@
 # ansible>=2.7 is needed for using the `k8s` resource
-# [mitogen currently doesn't work with ansible 2.8](https://github.com/dw/mitogen/issues/587)
-ansible>=2.7,<2.8
+ansible>=2.7
 behave-webdriver>=0.2.2
 certifi>=2019.3.9
 # Needed for ansible k8s resource
 openshift>=0.8.6
+# Needed for testinfra using the ansible module
+paramiko
 psutil>=5.5.0
 pycurl>=7.43.0.2
 pyopenssl>=19.0.0