Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ansible_mitogen: Fix usage of connection_loader__get. #1215

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ansible_mitogen/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +102 to +103
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 changing the names. ...__get = ...get_with_context has long bugged me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I saw that and thought like hell I'm leaving it like that hah

19 changes: 6 additions & 13 deletions ansible_mitogen/plugins/connection/mitogen_kubectl.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,32 +46,25 @@
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 '
'that ships with the "kubectl" connection plug-in.'
)

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
Expand Down
2 changes: 1 addition & 1 deletion ansible_mitogen/plugins/connection/mitogen_ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
56 changes: 38 additions & 18 deletions ansible_mitogen/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
import ansible.executor.process.worker
import ansible.template
import ansible.utils.sentinel
import ansible.playbook.play_context

from ansible.plugins.loader import get_with_context_result
Copy link
Member

@moreati moreati Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal preference, use absolute imports when possible. It makes it easier to distinguish what belongs to ansible.* and what belongs to ansible_mitogen.*, or mitogen.*

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha - I had some issues getting an absolute import working in this file due to other imports masking things. Tried a few more ways just now but didn't get anywhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried some variations in moreati@25da0f7. We could eliminate this import entirely by using

result = ansible_mitogen.loaders.action_loader__get_with_context(...)
...
return result._replace(object=adorned_klass)

It loses a little clarity. I'm in favour of the tradeoff, but ready to be convinced otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very keen on this alternative for a few reasons - clarity, as you mentioned, but also the fact that we're now relying on _replace which is a protected attribute. Adding to that, we've now also leaked the name of the tuple object into our code; meaning that if Ansible ever goes and changes it, our code is broken and needs an update.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very keen on this alternative

On reflection I agree. I'd still like to use an absolute import. I'll investigate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolute import seems to have worked in 8273eac



def _patch_awx_callback():
Expand Down Expand Up @@ -76,12 +79,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.
Expand All @@ -91,13 +94,19 @@ 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 get_with_context_result(adorned_klass, context)
return get_with_context_result(adorned_klass(*args, **kwargs), context)

return get_with_context_result(None, context)


REDIRECTED_CONNECTION_PLUGINS = (
Expand All @@ -115,15 +124,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):
Expand Down Expand Up @@ -173,8 +193,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
Expand All @@ -184,11 +204,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

Expand Down
1 change: 1 addition & 0 deletions tests/ansible/regression/all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions tests/ansible/regression/issue_766__get_with_context.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# https://github.com/mitogen-hq/mitogen/issues/776
---
- name: regression/issue_766__get_with_context.yml
hosts: localhost
gather_facts: true
tasks:
- block:
- name: Start container
delegate_to: localhost
vars:
ansible_port: 8040
containers.podman.podman_container:
name: sysprep
image: ghcr.io/mitogen-hq/sysrepo-netopeer2:latest
auto_remove: true
detach: true
recreate: true
published_ports:
- "{{ ansible_port }}:830"

- name: Wait for container
delegate_to: localhost
# TODO robust condition. wait_for + search_regex? wait_for_connection?
wait_for:
timeout: 5

- name: Get running configuration and state data
vars:
ansible_port: 8040
ansible_connection: netconf
ansible_user: netconf
ansible_password: netconf
ansible.netcommon.netconf_get:

always:
- name: Cleanup container
delegate_to: localhost
containers.podman.podman_container:
name: sysprep
state: absent
4 changes: 4 additions & 0 deletions tests/ansible/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@ paramiko==2.3.2 # Last 2.6-compat version.
# 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)
Loading