diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 0bf4556bf..8f110f13a 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -41,81 +41,6 @@ jobs: - template: azure-pipelines-steps.yml strategy: matrix: - Mito_27_centos6: - tox.env: py27-mode_mitogen-distro_centos6 - Mito_27_centos7: - tox.env: py27-mode_mitogen-distro_centos7 - Mito_27_centos8: - tox.env: py27-mode_mitogen-distro_centos8 - Mito_27_debian9: - tox.env: py27-mode_mitogen-distro_debian9 - Mito_27_debian10: - tox.env: py27-mode_mitogen-distro_debian10 - Mito_27_debian11: - tox.env: py27-mode_mitogen-distro_debian11 - Mito_27_ubuntu1604: - tox.env: py27-mode_mitogen-distro_ubuntu1604 - Mito_27_ubuntu1804: - tox.env: py27-mode_mitogen-distro_ubuntu1804 - Mito_27_ubuntu2004: - tox.env: py27-mode_mitogen-distro_ubuntu2004 - - Mito_36_centos6: - python.version: '3.6' - tox.env: py36-mode_mitogen-distro_centos6 - Mito_36_centos7: - python.version: '3.6' - tox.env: py36-mode_mitogen-distro_centos7 - Mito_36_centos8: - python.version: '3.6' - tox.env: py36-mode_mitogen-distro_centos8 - Mito_36_debian9: - python.version: '3.6' - tox.env: py36-mode_mitogen-distro_debian9 - Mito_36_debian10: - python.version: '3.6' - tox.env: py36-mode_mitogen-distro_debian10 - Mito_36_debian11: - python.version: '3.6' - tox.env: py36-mode_mitogen-distro_debian11 - Mito_36_ubuntu1604: - python.version: '3.6' - tox.env: py36-mode_mitogen-distro_ubuntu1604 - Mito_36_ubuntu1804: - python.version: '3.6' - tox.env: py36-mode_mitogen-distro_ubuntu1804 - Mito_36_ubuntu2004: - python.version: '3.6' - tox.env: py36-mode_mitogen-distro_ubuntu2004 - - Mito_312_centos6: - python.version: '3.12' - tox.env: py312-mode_mitogen-distro_centos6 - Mito_312_centos7: - python.version: '3.12' - tox.env: py312-mode_mitogen-distro_centos7 - Mito_312_centos8: - python.version: '3.12' - tox.env: py312-mode_mitogen-distro_centos8 - Mito_312_debian9: - python.version: '3.12' - tox.env: py312-mode_mitogen-distro_debian9 - Mito_312_debian10: - python.version: '3.12' - tox.env: py312-mode_mitogen-distro_debian10 - Mito_312_debian11: - python.version: '3.12' - tox.env: py312-mode_mitogen-distro_debian11 - Mito_312_ubuntu1604: - python.version: '3.12' - tox.env: py312-mode_mitogen-distro_ubuntu1604 - Mito_312_ubuntu1804: - python.version: '3.12' - tox.env: py312-mode_mitogen-distro_ubuntu1804 - Mito_312_ubuntu2004: - python.version: '3.12' - tox.env: py312-mode_mitogen-distro_ubuntu2004 - Ans_27_210: tox.env: py27-mode_ansible-ansible2.10 Ans_27_4: diff --git a/ansible_mitogen/mixins.py b/ansible_mitogen/mixins.py index 38f351eda..1b6512e85 100644 --- a/ansible_mitogen/mixins.py +++ b/ansible_mitogen/mixins.py @@ -36,8 +36,6 @@ import traceback import ansible -import ansible.constants -import ansible.plugins import ansible.plugins.action import ansible.utils.unsafe_proxy import ansible.vars.clean diff --git a/ansible_mitogen/plugins/connection/mitogen_local.py b/ansible_mitogen/plugins/connection/mitogen_local.py index 2d1e7052b..a651e6fca 100644 --- a/ansible_mitogen/plugins/connection/mitogen_local.py +++ b/ansible_mitogen/plugins/connection/mitogen_local.py @@ -33,11 +33,9 @@ import sys try: - import ansible_mitogen.connection + 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 + sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..'))) import ansible_mitogen.connection import ansible_mitogen.process diff --git a/ansible_mitogen/plugins/connection/mitogen_ssh.py b/ansible_mitogen/plugins/connection/mitogen_ssh.py index 75f2d42fb..36d86675a 100644 --- a/ansible_mitogen/plugins/connection/mitogen_ssh.py +++ b/ansible_mitogen/plugins/connection/mitogen_ssh.py @@ -32,56 +32,32 @@ import os.path import sys +import ansible.plugins.connection.ssh as _ansible_ssh + DOCUMENTATION = """ + name: mitogen_ssh author: David Wilson - connection: mitogen_ssh short_description: Connect over SSH via Mitogen description: - This connects using an OpenSSH client controlled by the Mitogen for Ansible extension. It accepts every option the vanilla ssh plugin accepts. - version_added: "2.5" options: - ssh_args: - type: str - vars: - - name: ssh_args - - name: ansible_ssh_args - - name: ansible_mitogen_ssh_args - ssh_common_args: - type: str - vars: - - name: ssh_args - - name: ansible_ssh_common_args - - name: ansible_mitogen_ssh_common_args - ssh_extra_args: - type: str - vars: - - name: ssh_args - - name: ansible_ssh_extra_args - - name: ansible_mitogen_ssh_extra_args -""" +""" + _ansible_ssh.DOCUMENTATION.partition('options:\n')[2] 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 + sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..'))) import ansible_mitogen.connection -import ansible_mitogen.loaders class Connection(ansible_mitogen.connection.Connection): transport = 'ssh' - vanilla_class = ansible_mitogen.loaders.connection_loader__get( - 'ssh', - class_only=True, - ) @staticmethod def _create_control_path(*args, **kwargs): """Forward _create_control_path() to the implementation in ssh.py.""" # https://github.com/dw/mitogen/issues/342 - return Connection.vanilla_class._create_control_path(*args, **kwargs) + return _ansible_ssh.Connection._create_control_path(*args, **kwargs) diff --git a/ansible_mitogen/plugins/strategy/mitogen.py b/ansible_mitogen/plugins/strategy/mitogen.py index abbe76726..21b04bd7d 100644 --- a/ansible_mitogen/plugins/strategy/mitogen.py +++ b/ansible_mitogen/plugins/strategy/mitogen.py @@ -47,12 +47,10 @@ # debuggers and isinstance() work predictably. # -BASE_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__), '../../..') -) - -if BASE_DIR not in sys.path: - sys.path.insert(0, BASE_DIR) +try: + import ansible_mitogen +except ImportError: + sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..'))) import ansible_mitogen.strategy import ansible.plugins.strategy.linear diff --git a/ansible_mitogen/plugins/strategy/mitogen_linear.py b/ansible_mitogen/plugins/strategy/mitogen_linear.py index b1b03aef3..e91e27d6e 100644 --- a/ansible_mitogen/plugins/strategy/mitogen_linear.py +++ b/ansible_mitogen/plugins/strategy/mitogen_linear.py @@ -47,12 +47,10 @@ # debuggers and isinstance() work predictably. # -BASE_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__), '../../..') -) - -if BASE_DIR not in sys.path: - sys.path.insert(0, BASE_DIR) +try: + import ansible_mitogen +except ImportError: + sys.path.insert(0, os.path.abspath(os.path.join(__file__, '../../../..'))) import ansible_mitogen.loaders import ansible_mitogen.strategy diff --git a/ansible_mitogen/transport_config.py b/ansible_mitogen/transport_config.py index 39df3f6a6..afd5c7096 100644 --- a/ansible_mitogen/transport_config.py +++ b/ansible_mitogen/transport_config.py @@ -62,7 +62,9 @@ __metaclass__ = type import abc +import logging import os + import ansible.utils.shlex import ansible.constants as C import ansible.executor.interpreter_discovery @@ -74,6 +76,8 @@ import mitogen.core +LOG = logging.getLogger(__name__) + def run_interpreter_discovery_if_necessary(s, task_vars, action, rediscover_python): """ Triggers ansible python interpreter discovery if requested. @@ -412,6 +416,29 @@ def __init__(self, connection, play_context, transport, inventory_name): # used to run interpreter discovery self._action = connection._action + def _become_option(self, name): + plugin = self._connection.become + if plugin is not None: + return plugin.get_option( + name, hostvars=self._task_vars, playcontext=self._play_context, + ) + else: + # FIXME BecomeBase.get_option() only does this for become_user, + # become_pass, become_flags, & become_exe. + LOG.warning( + '%r: Used play_context fallback for option %r', self, name, + ) + return getattr(self._play_context, name) + + def _connection_option(self, name): + try: + return self._connection.get_option(name, hostvars=self._task_vars) + except KeyError: + LOG.warning( + '%r: Used play_context fallback for option %r', self, name, + ) + return getattr(self._play_context, name) + def transport(self): return self._transport @@ -428,31 +455,20 @@ def become(self): return self._play_context.become def become_method(self): + # TODO self._connection.become.name? return self._play_context.become_method def become_user(self): - return self._play_context.become_user + return self._become_option('become_user') def become_pass(self): - # become_pass is owned/provided by the active become plugin. However - # PlayContext is intertwined with it. Known complications - # - ansible_become_password is higher priority than ansible_become_pass, - # `play_context.become_pass` doesn't obey this (atleast with Mitgeon). - # - `meta: reset_connection` runs `connection.reset()` but - # `ansible_mitogen.connection.Connection.reset()` recreates the - # connection object, setting `connection.become = None`. - become_plugin = self._connection.become - try: - become_pass = become_plugin.get_option('become_pass', playcontext=self._play_context) - except AttributeError: - become_pass = self._play_context.become_pass - return optional_secret(become_pass) + return optional_secret(self._become_option('become_pass')) def password(self): - return optional_secret(self._play_context.password) + return optional_secret(self._connection_option('password')) def port(self): - return self._play_context.port + return self._connection_option('port') def python_path(self, rediscover_python=False): s = self._connection.get_task_var('ansible_python_interpreter') @@ -466,21 +482,16 @@ def python_path(self, rediscover_python=False): rediscover_python=rediscover_python) def host_key_checking(self): - def candidates(): - yield self._connection.get_task_var('ansible_ssh_host_key_checking') - yield self._connection.get_task_var('ansible_host_key_checking') - yield C.HOST_KEY_CHECKING - val = next((v for v in candidates() if v is not None), True) - return boolean(val) + return self._connection_option('host_key_checking') def private_key_file(self): - return self._play_context.private_key_file + return self._connection_option('private_key_file') def ssh_executable(self): - return C.config.get_config_value("ssh_executable", plugin_type="connection", plugin_name="ssh", variables=self._task_vars.get("vars", {})) + return self._connection_option('ssh_executable') def timeout(self): - return self._play_context.timeout + return self._connection_option('timeout') def ansible_ssh_timeout(self): return ( @@ -490,42 +501,22 @@ def ansible_ssh_timeout(self): ) def ssh_args(self): - local_vars = self._task_vars.get("hostvars", {}).get(self._inventory_name, {}) return [ mitogen.core.to_text(term) for s in ( - C.config.get_config_value("ssh_args", plugin_type="connection", plugin_name="ssh", variables=local_vars), - C.config.get_config_value("ssh_common_args", plugin_type="connection", plugin_name="ssh", variables=local_vars), - C.config.get_config_value("ssh_extra_args", plugin_type="connection", plugin_name="ssh", variables=local_vars) + self._connection_option('ssh_args'), + self._connection_option('ssh_common_args'), + self._connection_option('ssh_extra_args'), ) for term in ansible.utils.shlex.shlex_split(s or '') ] def become_exe(self): - # 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 + return self._become_option('become_exe') def sudo_args(self): - return [ - mitogen.core.to_text(term) - for term in ansible.utils.shlex.shlex_split( - first_true(( - self._play_context.become_flags, - # Ansible <=2.7. - getattr(self._play_context, 'sudo_flags', ''), - # Ansible <=2.3. - getattr(C, 'DEFAULT_BECOME_FLAGS', ''), - getattr(C, 'DEFAULT_SUDO_FLAGS', '') - ), default='') - ) - ] + become_flags = self._become_option('become_flags') + return ansible.utils.shlex.shlex_split(become_flags or '') def mitogen_via(self): return self._connection.get_task_var('mitogen_via') diff --git a/tests/ansible/integration/connection_delegation/delegate_to_template.yml b/tests/ansible/integration/connection_delegation/delegate_to_template.yml index f9ad9e0bd..1bc16ca55 100644 --- a/tests/ansible/integration/connection_delegation/delegate_to_template.yml +++ b/tests/ansible/integration/connection_delegation/delegate_to_template.yml @@ -11,7 +11,6 @@ - name: integration/connection_delegation/delegate_to_template.yml vars: physical_host: "cd-normal-alias" - physical_hosts: ["cd-normal-alias", "cd-normal-normal"] hosts: test-targets gather_facts: no tasks: @@ -85,6 +84,71 @@ 'method': 'ssh', } ] + when: + - ansible_version.full is version('2.11.1', '>=', strict=True) + + - assert_equal: + left: out.result + right: [ + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'compression': True, + 'connect_timeout': 30, + 'hostname': 'alias-host', + 'identities_only': False, + 'identity_file': null, + 'keepalive_interval': 30, + 'keepalive_count': 10, + 'password': null, + 'port': null, + 'python_path': ['python3000'], + 'remote_name': null, + 'ssh_args': [ + -o, ControlMaster=auto, + -o, ControlPersist=60s, + -o, ForwardAgent=yes, + -o, HostKeyAlgorithms=+ssh-rsa, + -o, PubkeyAcceptedKeyTypes=+ssh-rsa, + -o, UserKnownHostsFile=/dev/null, + ], + 'ssh_debug_level': null, + 'ssh_path': 'ssh', + 'username': 'alias-user', + }, + 'method': 'ssh', + }, + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'compression': True, + 'connect_timeout': 30, + 'hostname': 'cd-normal-alias', + 'identities_only': False, + 'identity_file': null, + 'keepalive_interval': 30, + 'keepalive_count': 10, + 'password': null, + 'port': 22, + 'python_path': ['python3000'], + 'remote_name': null, + 'ssh_args': [ + -o, ControlMaster=auto, + -o, ControlPersist=60s, + -o, ForwardAgent=yes, + -o, HostKeyAlgorithms=+ssh-rsa, + -o, PubkeyAcceptedKeyTypes=+ssh-rsa, + -o, UserKnownHostsFile=/dev/null, + ], + 'ssh_debug_level': null, + 'ssh_path': 'ssh', + 'username': 'ansible-cfg-remote-user', + }, + 'method': 'ssh', + } + ] + when: + - ansible_version.full is version('2.11.1', '<', strict=True) tags: - delegate_to_template - mitogen_only diff --git a/tests/ansible/integration/connection_delegation/stack_construction.yml b/tests/ansible/integration/connection_delegation/stack_construction.yml index 8cf064bb4..9b7a835b7 100644 --- a/tests/ansible/integration/connection_delegation/stack_construction.yml +++ b/tests/ansible/integration/connection_delegation/stack_construction.yml @@ -90,6 +90,42 @@ 'method': 'ssh', }, ] + when: + - ansible_version.full is version('2.11.1', '>=', strict=True) + - assert_equal: + left: out.result + right: [ + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'compression': True, + 'connect_timeout': 30, + 'hostname': 'alias-host', + 'identities_only': False, + 'identity_file': null, + 'keepalive_interval': 30, + 'keepalive_count': 10, + 'password': null, + 'port': 22, + "python_path": ["python3000"], + 'remote_name': null, + 'ssh_args': [ + -o, ControlMaster=auto, + -o, ControlPersist=60s, + -o, ForwardAgent=yes, + -o, HostKeyAlgorithms=+ssh-rsa, + -o, PubkeyAcceptedKeyTypes=+ssh-rsa, + -o, UserKnownHostsFile=/dev/null, + ], + 'ssh_debug_level': null, + 'ssh_path': 'ssh', + 'username': 'alias-user', + }, + 'method': 'ssh', + }, + ] + when: + - ansible_version.full is version('2.11.1', '<', strict=True) tags: - mitogen_only - stack_construction @@ -132,6 +168,42 @@ 'method': 'ssh', }, ] + when: + - ansible_version.full is version('2.11.1', '>=', strict=True) + - assert_equal: + left: out.result + right: [ + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'compression': True, + 'connect_timeout': 30, + 'hostname': 'alias-host', + 'identities_only': False, + 'identity_file': null, + 'keepalive_interval': 30, + 'keepalive_count': 10, + 'password': null, + 'port': 22, + "python_path": ["python3000"], + 'remote_name': null, + 'ssh_args': [ + -o, ControlMaster=auto, + -o, ControlPersist=60s, + -o, ForwardAgent=yes, + -o, HostKeyAlgorithms=+ssh-rsa, + -o, PubkeyAcceptedKeyTypes=+ssh-rsa, + -o, UserKnownHostsFile=/dev/null, + ], + 'ssh_debug_level': null, + 'ssh_path': 'ssh', + 'username': 'alias-user', + }, + 'method': 'ssh', + }, + ] + when: + - ansible_version.full is version('2.11.1', '<', strict=True) tags: - mitogen_only - stack_construction @@ -185,6 +257,53 @@ 'method': 'ssh', }, ] + when: + - ansible_version.full is version('2.11.1', '>=', strict=True) + - assert_equal: + left: out.result + right: [ + { + 'kwargs': { + 'connect_timeout': 30, + 'doas_path': null, + 'password': null, + "python_path": ["python3000"], + 'remote_name': null, + 'username': 'normal-user', + }, + 'method': 'doas', + }, + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'compression': True, + 'connect_timeout': 30, + 'hostname': 'cd-normal-normal', + 'identities_only': False, + 'identity_file': null, + 'keepalive_interval': 30, + 'keepalive_count': 10, + 'password': null, + 'port': 22, + "python_path": ["python3000"], + 'remote_name': null, + 'ssh_args': [ + -o, ControlMaster=auto, + -o, ControlPersist=60s, + -o, ForwardAgent=yes, + -o, HostKeyAlgorithms=+ssh-rsa, + -o, PubkeyAcceptedKeyTypes=+ssh-rsa, + -o, UserKnownHostsFile=/dev/null, + ], + 'ssh_debug_level': null, + 'ssh_path': 'ssh', + 'username': 'ansible-cfg-remote-user', + }, + 'method': 'ssh', + }, + ] + when: + - ansible_version.full is version('2.11.1', '<', strict=True) tags: - mitogen_only - stack_construction @@ -255,6 +374,70 @@ 'method': 'ssh', }, ] + when: + - ansible_version.full is version('2.11.1', '>=', strict=True) + - assert_equal: + left: out.result + right: [ + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'compression': True, + 'connect_timeout': 30, + 'hostname': 'alias-host', + 'identities_only': False, + 'identity_file': null, + 'keepalive_interval': 30, + 'keepalive_count': 10, + 'password': null, + 'port': null, + "python_path": ["python3000"], + 'remote_name': null, + 'ssh_args': [ + -o, ControlMaster=auto, + -o, ControlPersist=60s, + -o, ForwardAgent=yes, + -o, HostKeyAlgorithms=+ssh-rsa, + -o, PubkeyAcceptedKeyTypes=+ssh-rsa, + -o, UserKnownHostsFile=/dev/null, + ], + 'ssh_debug_level': null, + 'ssh_path': 'ssh', + 'username': 'alias-user', + }, + 'method': 'ssh', + }, + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'compression': True, + 'connect_timeout': 30, + 'hostname': 'cd-normal-alias', + 'identities_only': False, + 'identity_file': null, + 'keepalive_interval': 30, + 'keepalive_count': 10, + 'password': null, + 'port': 22, + "python_path": ["python3000"], + 'remote_name': null, + 'ssh_args': [ + -o, ControlMaster=auto, + -o, ControlPersist=60s, + -o, ForwardAgent=yes, + -o, HostKeyAlgorithms=+ssh-rsa, + -o, PubkeyAcceptedKeyTypes=+ssh-rsa, + -o, UserKnownHostsFile=/dev/null, + ], + 'ssh_debug_level': null, + 'ssh_path': 'ssh', + 'username': 'ansible-cfg-remote-user', + }, + 'method': 'ssh', + }, + ] + when: + - ansible_version.full is version('2.11.1', '<', strict=True) tags: - stack_construction @@ -307,6 +490,53 @@ 'method': 'ssh', }, ] + when: + - ansible_version.full is version('2.11.1', '>=', strict=True) + - assert_equal: + left: out.result + right: [ + { + 'kwargs': { + 'connect_timeout': 30, + 'doas_path': null, + 'password': null, + "python_path": ["python3000"], + 'remote_name': null, + 'username': 'normal-user', + }, + 'method': 'doas', + }, + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'compression': True, + 'connect_timeout': 30, + 'hostname': 'cd-newuser-normal-normal', + 'identities_only': False, + 'identity_file': null, + 'keepalive_interval': 30, + 'keepalive_count': 10, + 'password': null, + 'port': 22, + "python_path": ["python3000"], + 'remote_name': null, + 'ssh_args': [ + -o, ControlMaster=auto, + -o, ControlPersist=60s, + -o, ForwardAgent=yes, + -o, HostKeyAlgorithms=+ssh-rsa, + -o, PubkeyAcceptedKeyTypes=+ssh-rsa, + -o, UserKnownHostsFile=/dev/null, + ], + 'ssh_debug_level': null, + 'ssh_path': 'ssh', + 'username': 'newuser-normal-normal-user', + }, + 'method': 'ssh', + }, + ] + when: + - ansible_version.full is version('2.11.1', '<', strict=True) tags: - mitogen_only - stack_construction @@ -350,6 +580,42 @@ 'method': 'ssh', }, ] + when: + - ansible_version.full is version('2.11.1', '>=', strict=True) + - assert_equal: + left: out.result + right: [ + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'compression': True, + 'connect_timeout': 30, + 'hostname': 'alias-host', + 'identities_only': False, + 'identity_file': null, + 'keepalive_interval': 30, + 'keepalive_count': 10, + 'password': null, + 'port': 22, + "python_path": ["python3000"], + 'remote_name': null, + 'ssh_args': [ + -o, ControlMaster=auto, + -o, ControlPersist=60s, + -o, ForwardAgent=yes, + -o, HostKeyAlgorithms=+ssh-rsa, + -o, PubkeyAcceptedKeyTypes=+ssh-rsa, + -o, UserKnownHostsFile=/dev/null, + ], + 'ssh_debug_level': null, + 'ssh_path': 'ssh', + 'username': 'alias-user', + }, + 'method': 'ssh', + }, + ] + when: + - ansible_version.full is version('2.11.1', '<', strict=True) tags: - mitogen_only - stack_construction diff --git a/tests/ansible/integration/transport_config/port.yml b/tests/ansible/integration/transport_config/port.yml index 1b8e04a04..317d3e643 100644 --- a/tests/ansible/integration/transport_config/port.yml +++ b/tests/ansible/integration/transport_config/port.yml @@ -11,7 +11,8 @@ that: - out.result|length == 1 - out.result[0].method == "ssh" - - out.result[0].kwargs.port == None + - (ansible_version.full is version('2.11.1', '>=', strict=True) and out.result[0].kwargs.port == None) + or out.result[0].kwargs.port == 22 fail_msg: | out={{ out }} tags: @@ -61,7 +62,8 @@ - out.result[0].method == "ssh" - out.result[0].kwargs.port == 4321 - out.result[1].method == "ssh" - - out.result[1].kwargs.port == None + - (ansible_version.full is version('2.11.1', '>=', strict=True) and out.result[1].kwargs.port == None) + or out.result[1].kwargs.port == 22 fail_msg: | out={{ out }} tags: @@ -93,7 +95,8 @@ - out.result[0].method == "ssh" - out.result[0].kwargs.port == 1234 - out.result[1].method == "ssh" - - out.result[1].kwargs.port == None + - (ansible_version.full is version('2.11.1', '>=', strict=True) and out.result[1].kwargs.port == None) + or out.result[1].kwargs.port == 22 fail_msg: | out={{ out }} tags: @@ -126,7 +129,8 @@ - out.result[0].method == "ssh" - out.result[0].kwargs.port == 1532 - out.result[1].method == "ssh" - - out.result[1].kwargs.port == None + - (ansible_version.full is version('2.11.1', '>=', strict=True) and out.result[1].kwargs.port == None) + or out.result[1].kwargs.port == 22 fail_msg: | out={{ out }} tags: diff --git a/tests/ansible/lib/action/assert_equal.py b/tests/ansible/lib/action/assert_equal.py index 72264cf6e..6f6a20243 100644 --- a/tests/ansible/lib/action/assert_equal.py +++ b/tests/ansible/lib/action/assert_equal.py @@ -7,10 +7,37 @@ __metaclass__ = type import inspect +import types import unittest +import ansible.plugins.loader import ansible.template +try: + from ansible.template import AnsibleNativeEnvironment +except ImportError: + import ansible.template.template + import jinja2.nativetypes + + class AnsibleNativeEnvironment(jinja2.nativetypes.NativeEnvironment): + context_class = ansible.template.AnsibleContext + template_class = ansible.template.template.AnsibleJ2Template + + def __init__(self, *args, **kwargs): + super(AnsibleNativeEnvironment, self).__init__(*args, **kwargs) + + self.filters = ansible.template.JinjaPluginIntercept( + self.filters, + ansible.plugins.loader.filter_loader, + jinja2_native=True, + ) + self.tests = ansible.template.JinjaPluginIntercept( + self.tests, + ansible.plugins.loader.test_loader, + jinja2_native=True + ) + + from ansible.plugins.action import ActionBase @@ -41,18 +68,54 @@ def text_diff(a, b): return str(e) +def _finalize(self, thing): + if thing in (None, 22): + return thing + else: + return self._finalize_orig(thing) + class ActionModule(ActionBase): ''' Fail with custom message ''' TRANSFERS_FILES = False _VALID_ARGS = frozenset(('left', 'right')) + def __inijjt__(self, task, connection, play_context, loader, templar, shared_loader_obj): + super(ActionModule, self).__init__( + task, + connection, + play_context, + loader, + templar.copy_with_new_env(AnsibleNativeEnvironment), + shared_loader_obj, + ) + def template(self, obj): return self._templar.template( obj, convert_bare=True, **TEMPLATE_KWARGS ) + templar = self._templar + environment = templar.environment + try: + templar._finalize_orig = templar._finalize + environment._finalize_orig = environment.finalize + templar._finalize = types.MethodType(_finalize, templar) + environment.finalize = types.MethodType(_finalize, environment) + + result = templar.template( + obj, + convert_bare=True, + **TEMPLATE_KWARGS + ) + finally: + templar._finalize = templar._finalize_orig + environment.finalize = environment._finalize_orig + del templar._finalize_orig + del environment._finalize_orig + + return result def run(self, tmp=None, task_vars=None): result = super(ActionModule, self).run(tmp, task_vars or {}) diff --git a/tox.ini b/tox.ini index 9fb31bdcb..44425ae49 100644 --- a/tox.ini +++ b/tox.ini @@ -2,8 +2,7 @@ # I use this locally on Ubuntu 22.04, with the following additions # # sudo add-apt-repository ppa:deadsnakes/ppa -# sudo apt update -# sudo apt install awscli lib{ldap2,sasl2,ssl}-dev python{2,2.7,3} python3.{6..13}{,-venv} python-is-python3 sshpass tox +# sudo apt install lib{ldap2,sasl2,ssl}-dev python{2,2.7,3}{,-dev} python3.{7..13}{,-dev,-venv} python-is-python3 sshpass tox # Py A cntrllr A target coverage Django Jinja2 pip psutil pytest tox virtualenv # ==== ========== ========== ========== ========== ========== ========== ========== ========== ========== ==========