diff --git a/ansible_mitogen/loaders.py b/ansible_mitogen/loaders.py
index 632b11b14..123dd4ac6 100644
--- a/ansible_mitogen/loaders.py
+++ b/ansible_mitogen/loaders.py
@@ -99,5 +99,5 @@ def assert_supported_release():
from ansible.plugins.loader import strategy_loader
# These are original, unwrapped implementations
-action_loader__get = action_loader.get
-connection_loader__get = connection_loader.get_with_context
+action_loader__get_with_context = action_loader.get_with_context
+connection_loader__get_with_context = connection_loader.get_with_context
diff --git a/ansible_mitogen/planner.py b/ansible_mitogen/planner.py
index 4cdc0f206..6490afce2 100644
--- a/ansible_mitogen/planner.py
+++ b/ansible_mitogen/planner.py
@@ -341,6 +341,47 @@ def get_module_deps(self):
'firewalld', # issue #570: ansible module_utils caches dbus conn
'ansible.legacy.dnf', # issue #776
'ansible.builtin.dnf', # issue #832
+ 'freeipa.ansible_freeipa.ipaautomember', # issue #1216
+ 'freeipa.ansible_freeipa.ipaautomountkey',
+ 'freeipa.ansible_freeipa.ipaautomountlocation',
+ 'freeipa.ansible_freeipa.ipaautomountmap',
+ 'freeipa.ansible_freeipa.ipacert',
+ 'freeipa.ansible_freeipa.ipaconfig',
+ 'freeipa.ansible_freeipa.ipadelegation',
+ 'freeipa.ansible_freeipa.ipadnsconfig',
+ 'freeipa.ansible_freeipa.ipadnsforwardzone',
+ 'freeipa.ansible_freeipa.ipadnsrecord',
+ 'freeipa.ansible_freeipa.ipadnszone',
+ 'freeipa.ansible_freeipa.ipagroup',
+ 'freeipa.ansible_freeipa.ipahbacrule',
+ 'freeipa.ansible_freeipa.ipahbacsvc',
+ 'freeipa.ansible_freeipa.ipahbacsvcgroup',
+ 'freeipa.ansible_freeipa.ipahost',
+ 'freeipa.ansible_freeipa.ipahostgroup',
+ 'freeipa.ansible_freeipa.idoverridegroup',
+ 'freeipa.ansible_freeipa.idoverrideuser',
+ 'freeipa.ansible_freeipa.idp',
+ 'freeipa.ansible_freeipa.idrange',
+ 'freeipa.ansible_freeipa.idview',
+ 'freeipa.ansible_freeipa.ipalocation',
+ 'freeipa.ansible_freeipa.ipanetgroup',
+ 'freeipa.ansible_freeipa.ipapermission',
+ 'freeipa.ansible_freeipa.ipaprivilege',
+ 'freeipa.ansible_freeipa.ipapwpolicy',
+ 'freeipa.ansible_freeipa.iparole',
+ 'freeipa.ansible_freeipa.ipaselfservice',
+ 'freeipa.ansible_freeipa.ipaserver',
+ 'freeipa.ansible_freeipa.ipaservice',
+ 'freeipa.ansible_freeipa.ipaservicedelegationrule',
+ 'freeipa.ansible_freeipa.ipaservicedelegationtarget',
+ 'freeipa.ansible_freeipa.ipasudocmd',
+ 'freeipa.ansible_freeipa.ipasudocmdgroup',
+ 'freeipa.ansible_freeipa.ipasudorule',
+ 'freeipa.ansible_freeipa.ipatopologysegment',
+ 'freeipa.ansible_freeipa.ipatopologysuffix',
+ 'freeipa.ansible_freeipa.ipatrust',
+ 'freeipa.ansible_freeipa.ipauser',
+ 'freeipa.ansible_freeipa.ipavault',
])
def should_fork(self):
diff --git a/ansible_mitogen/plugins/connection/mitogen_kubectl.py b/ansible_mitogen/plugins/connection/mitogen_kubectl.py
index bae41609c..33ae49e6a 100644
--- a/ansible_mitogen/plugins/connection/mitogen_kubectl.py
+++ b/ansible_mitogen/plugins/connection/mitogen_kubectl.py
@@ -46,14 +46,12 @@
import ansible_mitogen.loaders
-_get_result = ansible_mitogen.loaders.connection_loader__get(
- 'kubectl',
- class_only=True,
-)
-
-
class Connection(ansible_mitogen.connection.Connection):
transport = 'kubectl'
+ (vanilla_class, load_context) = ansible_mitogen.loaders.connection_loader__get_with_context(
+ 'kubectl',
+ class_only=True,
+ )
not_supported_msg = (
'The "mitogen_kubectl" plug-in requires a version of Ansible '
@@ -61,17 +59,12 @@ class Connection(ansible_mitogen.connection.Connection):
)
def __init__(self, *args, **kwargs):
- if not _get_result:
+ if not Connection.vanilla_class:
raise ansible.errors.AnsibleConnectionFailure(self.not_supported_msg)
super(Connection, self).__init__(*args, **kwargs)
def get_extra_args(self):
- try:
- # Ansible < 2.10, _get_result is the connection class
- connection_options = _get_result.connection_options
- except AttributeError:
- # Ansible >= 2.10, _get_result is a get_with_context_result
- connection_options = _get_result.object.connection_options
+ connection_options = Connection.vanilla_class.connection_options
parameters = []
for key in connection_options:
task_var_name = 'ansible_%s' % key
diff --git a/ansible_mitogen/plugins/connection/mitogen_ssh.py b/ansible_mitogen/plugins/connection/mitogen_ssh.py
index f6a27a6e7..b953edba3 100644
--- a/ansible_mitogen/plugins/connection/mitogen_ssh.py
+++ b/ansible_mitogen/plugins/connection/mitogen_ssh.py
@@ -60,7 +60,7 @@
class Connection(ansible_mitogen.connection.Connection):
transport = 'ssh'
- vanilla_class = ansible_mitogen.loaders.connection_loader__get(
+ (vanilla_class, load_context) = ansible_mitogen.loaders.connection_loader__get_with_context(
'ssh',
class_only=True,
)
diff --git a/ansible_mitogen/strategy.py b/ansible_mitogen/strategy.py
index 440e58112..9408ca636 100644
--- a/ansible_mitogen/strategy.py
+++ b/ansible_mitogen/strategy.py
@@ -47,6 +47,8 @@
import ansible.executor.process.worker
import ansible.template
import ansible.utils.sentinel
+import ansible.playbook.play_context
+import ansible.plugins.loader
def _patch_awx_callback():
@@ -76,12 +78,12 @@ def patch_add_local(self, **kwargs):
_patch_awx_callback()
-def wrap_action_loader__get(name, *args, **kwargs):
+def wrap_action_loader__get_with_context(name, *args, **kwargs):
"""
- While the mitogen strategy is active, trap action_loader.get() calls,
- augmenting any fetched class with ActionModuleMixin, which replaces various
- helper methods inherited from ActionBase with implementations that avoid
- the use of shell fragments wherever possible.
+ While the mitogen strategy is active, trap action_loader.get_with_context()
+ calls, augmenting any fetched class with ActionModuleMixin, which replaces
+ various helper methods inherited from ActionBase with implementations that
+ avoid the use of shell fragments wherever possible.
This is used instead of static subclassing as it generalizes to third party
action plugins outside the Ansible tree.
@@ -91,13 +93,26 @@ def wrap_action_loader__get(name, *args, **kwargs):
name = 'mitogen_' + name
get_kwargs['collection_list'] = kwargs.pop('collection_list', None)
- klass = ansible_mitogen.loaders.action_loader__get(name, **get_kwargs)
+ (klass, context) = ansible_mitogen.loaders.action_loader__get_with_context(
+ name,
+ **get_kwargs
+ )
+
if klass:
bases = (ansible_mitogen.mixins.ActionModuleMixin, klass)
adorned_klass = type(str(name), bases, {})
if kwargs.get('class_only'):
- return adorned_klass
- return adorned_klass(*args, **kwargs)
+ return ansible.plugins.loader.get_with_context_result(
+ adorned_klass,
+ context
+ )
+
+ return ansible.plugins.loader.get_with_context_result(
+ adorned_klass(*args, **kwargs),
+ context
+ )
+
+ return ansible.plugins.loader.get_with_context_result(None, context)
REDIRECTED_CONNECTION_PLUGINS = (
@@ -115,15 +130,26 @@ def wrap_action_loader__get(name, *args, **kwargs):
)
-def wrap_connection_loader__get(name, *args, **kwargs):
+def wrap_connection_loader__get_with_context(name, *args, **kwargs):
"""
- While a Mitogen strategy is active, rewrite connection_loader.get() calls
- for some transports into requests for a compatible Mitogen transport.
+ While a Mitogen strategy is active, rewrite
+ connection_loader.get_with_context() calls for some transports into
+ requests for a compatible Mitogen transport.
"""
- if name in REDIRECTED_CONNECTION_PLUGINS:
+ is_play_using_mitogen_connection = None
+ if len(args) > 0 and isinstance(args[0], ansible.playbook.play_context.PlayContext):
+ play_context = args[0]
+ is_play_using_mitogen_connection = play_context.connection in REDIRECTED_CONNECTION_PLUGINS
+
+ # assume true if we're not in a play context since we're using a Mitogen strategy
+ if is_play_using_mitogen_connection is None:
+ is_play_using_mitogen_connection = True
+
+ redirect_connection = name in REDIRECTED_CONNECTION_PLUGINS and is_play_using_mitogen_connection
+ if redirect_connection:
name = 'mitogen_' + name
- return ansible_mitogen.loaders.connection_loader__get(name, *args, **kwargs)
+ return ansible_mitogen.loaders.connection_loader__get_with_context(name, *args, **kwargs)
def wrap_worker__run(self):
@@ -173,8 +199,8 @@ def _install_wrappers(self):
Install our PluginLoader monkey patches and update global variables
with references to the real functions.
"""
- ansible_mitogen.loaders.action_loader.get = wrap_action_loader__get
- ansible_mitogen.loaders.connection_loader.get_with_context = wrap_connection_loader__get
+ ansible_mitogen.loaders.action_loader.get_with_context = wrap_action_loader__get_with_context
+ ansible_mitogen.loaders.connection_loader.get_with_context = wrap_connection_loader__get_with_context
global worker__run
worker__run = ansible.executor.process.worker.WorkerProcess.run
@@ -184,11 +210,11 @@ def _remove_wrappers(self):
"""
Uninstall the PluginLoader monkey patches.
"""
- ansible_mitogen.loaders.action_loader.get = (
- ansible_mitogen.loaders.action_loader__get
+ ansible_mitogen.loaders.action_loader.get_with_context = (
+ ansible_mitogen.loaders.action_loader__get_with_context
)
ansible_mitogen.loaders.connection_loader.get_with_context = (
- ansible_mitogen.loaders.connection_loader__get
+ ansible_mitogen.loaders.connection_loader__get_with_context
)
ansible.executor.process.worker.WorkerProcess.run = worker__run
diff --git a/docs/changelog.rst b/docs/changelog.rst
index f4a70ed0c..7cd795abe 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -18,6 +18,17 @@ To avail of fixes in an unreleased version, please download a ZIP file
`directly from GitHub `_.
+v0.3.21 (2025-01-20)
+--------------------
+
+* :gh:issue:`1209` docs: Fix Netlify build of website
+* :gh:issue:`1216` :mod:`ansible_mitogen`: Add all ansible_freeipa modules to
+ the always-fork list.
+* :gh:issue:`766` :mod:`ansible_mitogen`: Fix ""could not recover task_vars"
+ and "get_with_context_result object has no attribute _create_control_path"
+ when using ``kubectl``, ``netconf``, or ``network_cli`` connection plugins.
+
+
v0.3.20 (2025-01-07)
--------------------
diff --git a/docs/conf.py b/docs/conf.py
index b7dd15250..9ad5b534b 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,8 +1,21 @@
+import re
import sys
sys.path.append('.')
-VERSION = '0.3.9'
+
+def changelog_version(path, encoding='utf-8'):
+ version_pattern = re.compile(
+ r'^v(?P[0-9]+\.[0-9]+\.[0-9]+)',
+ re.MULTILINE,
+ )
+
+ with open(path, encoding=encoding) as f:
+ match = version_pattern.search(f.read())
+ return match.group('version')
+
+
+VERSION = changelog_version('changelog.rst')
author = u'Network Genomics'
copyright = u'2021, the Mitogen authors'
diff --git a/docs/contributors.rst b/docs/contributors.rst
index ad35f91cf..c37b64c91 100644
--- a/docs/contributors.rst
+++ b/docs/contributors.rst
@@ -124,6 +124,7 @@ sponsorship and outstanding future-thinking of its early adopters.
Epartment
Fidy Andrianaivo — never let a human do an ansible job ;)
rkrzr
+ Jarl Gullberg
jgadling
John F Wall — Making Ansible Great with Massive Parallelism
Jonathan Rosser
@@ -132,6 +133,7 @@ sponsorship and outstanding future-thinking of its early adopters.
Luca Berruti
Lewis Bellwood — Happy to be apart of a great project.
luto
+ @markafarrell
Mayeu a.k.a Matthieu Maury
Michael D'Silva
mordek
diff --git a/docs/netlify.toml b/docs/netlify.toml
new file mode 100644
index 000000000..8b1f8d245
--- /dev/null
+++ b/docs/netlify.toml
@@ -0,0 +1,2 @@
+[build.environment]
+PYTHON_VERSION = "3.8"
diff --git a/mitogen/__init__.py b/mitogen/__init__.py
index 08a3bc58a..bbf807dcd 100644
--- a/mitogen/__init__.py
+++ b/mitogen/__init__.py
@@ -35,7 +35,7 @@
#: Library version as a tuple.
-__version__ = (0, 3, 20)
+__version__ = (0, 3, 21)
#: This is :data:`False` in slave contexts. Previously it was used to prevent
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 000000000..8b1f8d245
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,2 @@
+[build.environment]
+PYTHON_VERSION = "3.8"
diff --git a/tests/ansible/regression/all.yml b/tests/ansible/regression/all.yml
index a7c8033e4..70b5ffe77 100644
--- a/tests/ansible/regression/all.yml
+++ b/tests/ansible/regression/all.yml
@@ -13,6 +13,7 @@
- import_playbook: issue_591__setuptools_cwd_crash.yml
- import_playbook: issue_615__streaming_transfer.yml
- import_playbook: issue_655__wait_for_connection_error.yml
+- import_playbook: issue_766__get_with_context.yml
- import_playbook: issue_776__load_plugins_called_twice.yml
- import_playbook: issue_952__ask_become_pass.yml
- import_playbook: issue_1066__add_host__host_key_checking.yml
diff --git a/tests/ansible/regression/issue_655__wait_for_connection_error.yml b/tests/ansible/regression/issue_655__wait_for_connection_error.yml
index a1f39f66a..506d5516e 100644
--- a/tests/ansible/regression/issue_655__wait_for_connection_error.yml
+++ b/tests/ansible/regression/issue_655__wait_for_connection_error.yml
@@ -11,7 +11,10 @@
tasks:
- meta: end_play
when:
- # Podman versions available in Homebrew have dropped macOS 12 support.
+ # Podman versions available in Homebrew require macOS 13+ (Ventura).
+ # https://formulae.brew.sh/formula/podman
+ # See also
+ # - issue_766__get_with_context.yml
- ansible_facts.system == 'Darwin'
- ansible_facts.distribution_version is version('13.0', '<', strict=True)
diff --git a/tests/ansible/regression/issue_766__get_with_context.yml b/tests/ansible/regression/issue_766__get_with_context.yml
new file mode 100644
index 000000000..e26ed9f2d
--- /dev/null
+++ b/tests/ansible/regression/issue_766__get_with_context.yml
@@ -0,0 +1,64 @@
+# https://github.com/mitogen-hq/mitogen/issues/776
+---
+- name: regression/issue_766__get_with_context.yml
+ hosts: localhost
+ # Gather facts to use *and* to trigger any "could not recover task_vars" error
+ # https://github.com/mitogen-hq/mitogen/pull/1215#issuecomment-2596421111
+ gather_facts: true
+ vars:
+ netconf_container_image: ghcr.io/mitogen-hq/sysrepo-netopeer2:latest
+ netconf_container_name: sysprep
+ netconf_container_port: 8030
+
+ tasks:
+ - meta: end_play
+ when:
+ # Podman can be installed on macOS, but authenticating to gchr.io isn't
+ # worth the trouble right now.
+ # See also
+ # - issue_655__wait_for_connection_error.yml
+ - ansible_facts.system == 'Darwin'
+
+ - meta: end_play
+ when:
+ # A failure during the ansible.netcommon.netconf_get task, when run
+ # with Ansible 4 (ansible-core 2.11) & associated collections.
+ # ansible.module_utils.connection.ConnectionError: Method not found
+ # https://github.com/mitogen-hq/mitogen/actions/runs/12854359099/job/35838635886
+ - ansible_version.full is version('2.11', '>=', strict=True)
+ - ansible_version.full is version('2.12', '<', strict=True)
+
+ - block:
+ - name: Start container
+ command:
+ cmd: >-
+ podman run
+ --name "{{ netconf_container_name }}"
+ --detach
+ --rm
+ --publish "{{ netconf_container_port }}:830"
+ "{{ netconf_container_image }}"
+ changed_when: true
+
+ - name: Wait for container
+ # TODO robust condition. wait_for + search_regex? wait_for_connection?
+ wait_for:
+ timeout: 5
+
+ - name: Get running configuration and state data
+ vars:
+ ansible_connection: netconf
+ ansible_user: netconf
+ ansible_password: netconf
+ ansible_port: "{{ netconf_container_port }}"
+ ansible_host_key_checking: false
+ ansible_python_interpreter: "{{ ansible_playbook_python }}"
+ ansible.netcommon.netconf_get:
+
+ always:
+ - name: Cleanup container
+ command:
+ cmd: podman stop "{{ netconf_container_name }}"
+ changed_when: true
+ tags:
+ - issue_766
diff --git a/tests/ansible/requirements.txt b/tests/ansible/requirements.txt
index 8cfb348ab..11457e66d 100644
--- a/tests/ansible/requirements.txt
+++ b/tests/ansible/requirements.txt
@@ -1,7 +1,13 @@
-paramiko==2.3.2 # Last 2.6-compat version.
+paramiko==2.12.0; python_version <= '2.7'
+paramiko==3.5.0; python_version >= '3.6'
+
# Incompatible with pip >= 72, due to removal of `setup.py test`:
# ModuleNotFoundError: No module named 'setuptools.command.test'
# https://github.com/pypa/setuptools/issues/4519
hdrhistogram==0.6.1
+
+ncclient==0.6.13; python_version <= '2.7'
+ncclient==0.6.16; python_version > '2.7'
+
PyYAML==3.11; python_version < '2.7'
PyYAML==5.3.1; python_version >= '2.7' # Latest release (Jan 2021)