diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 0bf4556bf..81c8efe0c 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -17,6 +17,8 @@ trigger: jobs: - job: mac12 + variables: + ANSIBLE_VERBOSITY: 3 # vanilla Ansible is really slow timeoutInMinutes: 120 steps: @@ -26,8 +28,6 @@ jobs: vmImage: macOS-12 strategy: matrix: - Mito_312: - tox.env: py312-mode_mitogen Loc_312_10: tox.env: py312-mode_localhost-ansible10 Van_312_10: @@ -41,117 +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: - tox.env: py27-mode_ansible-ansible4 - - Ans_36_210: - python.version: '3.6' - tox.env: py36-mode_ansible-ansible2.10 - Ans_36_4: - python.version: '3.6' - tox.env: py36-mode_ansible-ansible4 - - Ans_311_210: - python.version: '3.11' - tox.env: py311-mode_ansible-ansible2.10 - Ans_311_3: - python.version: '3.11' - tox.env: py311-mode_ansible-ansible3 - Ans_311_4: - python.version: '3.11' - tox.env: py311-mode_ansible-ansible4 - Ans_311_5: - python.version: '3.11' - tox.env: py311-mode_ansible-ansible5 - Ans_312_6: - python.version: '3.12' - tox.env: py312-mode_ansible-ansible6 - Ans_312_7: - python.version: '3.12' - tox.env: py312-mode_ansible-ansible7 - Ans_312_8: - python.version: '3.12' - tox.env: py312-mode_ansible-ansible8 - Ans_312_9: - python.version: '3.12' - tox.env: py312-mode_ansible-ansible9 Ans_312_10: python.version: '3.12' tox.env: py312-mode_ansible-ansible10 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4520c3cfb..a1845e4ac 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,42 +24,6 @@ jobs: fail-fast: false matrix: include: - - name: Ans_27_210 - tox_env: py27-mode_ansible-ansible2.10 - - name: Ans_27_4 - tox_env: py27-mode_ansible-ansible4 - - - name: Ans_36_210 - python_version: '3.6' - tox_env: py36-mode_ansible-ansible2.10 - - name: Ans_36_4 - python_version: '3.6' - tox_env: py36-mode_ansible-ansible4 - - - name: Ans_311_210 - python_version: '3.11' - tox_env: py311-mode_ansible-ansible2.10 - - name: Ans_311_3 - python_version: '3.11' - tox_env: py311-mode_ansible-ansible3 - - name: Ans_311_4 - python_version: '3.11' - tox_env: py311-mode_ansible-ansible4 - - name: Ans_311_5 - python_version: '3.11' - tox_env: py311-mode_ansible-ansible5 - - name: Ans_312_6 - python_version: '3.12' - tox_env: py312-mode_ansible-ansible6 - - name: Ans_312_7 - python_version: '3.12' - tox_env: py312-mode_ansible-ansible7 - - name: Ans_312_8 - python_version: '3.12' - tox_env: py312-mode_ansible-ansible8 - - name: Ans_312_9 - python_version: '3.12' - tox_env: py312-mode_ansible-ansible9 - name: Ans_312_10 python_version: '3.12' tox_env: py312-mode_ansible-ansible10 @@ -67,80 +31,6 @@ jobs: python_version: '3.12' tox_env: py312-mode_ansible-ansible10-strategy_linear - - name: Mito_27_centos6 - tox_env: py27-mode_mitogen-distro_centos6 - - name: Mito_27_centos7 - tox_env: py27-mode_mitogen-distro_centos7 - - name: Mito_27_centos8 - tox_env: py27-mode_mitogen-distro_centos8 - - name: Mito_27_debian9 - tox_env: py27-mode_mitogen-distro_debian9 - - name: Mito_27_debian10 - tox_env: py27-mode_mitogen-distro_debian10 - - name: Mito_27_debian11 - tox_env: py27-mode_mitogen-distro_debian11 - - name: Mito_27_ubuntu1604 - tox_env: py27-mode_mitogen-distro_ubuntu1604 - - name: Mito_27_ubuntu1804 - tox_env: py27-mode_mitogen-distro_ubuntu1804 - - name: Mito_27_ubuntu2004 - tox_env: py27-mode_mitogen-distro_ubuntu2004 - - - name: Mito_36_centos6 - python_version: '3.6' - tox_env: py36-mode_mitogen-distro_centos6 - - name: Mito_36_centos7 - python_version: '3.6' - tox_env: py36-mode_mitogen-distro_centos7 - - name: Mito_36_centos8 - python_version: '3.6' - tox_env: py36-mode_mitogen-distro_centos8 - - name: Mito_36_debian9 - python_version: '3.6' - tox_env: py36-mode_mitogen-distro_debian9 - - name: Mito_36_debian10 - python_version: '3.6' - tox_env: py36-mode_mitogen-distro_debian10 - - name: Mito_36_debian11 - python_version: '3.6' - tox_env: py36-mode_mitogen-distro_debian11 - - name: Mito_36_ubuntu1604 - python_version: '3.6' - tox_env: py36-mode_mitogen-distro_ubuntu1604 - - name: Mito_36_ubuntu1804 - python_version: '3.6' - tox_env: py36-mode_mitogen-distro_ubuntu1804 - - name: Mito_36_ubuntu2004 - python_version: '3.6' - tox_env: py36-mode_mitogen-distro_ubuntu2004 - - - name: Mito_312_centos6 - python_version: '3.12' - tox_env: py312-mode_mitogen-distro_centos6 - - name: Mito_312_centos7 - python_version: '3.12' - tox_env: py312-mode_mitogen-distro_centos7 - - name: Mito_312_centos8 - python_version: '3.12' - tox_env: py312-mode_mitogen-distro_centos8 - - name: Mito_312_debian9 - python_version: '3.12' - tox_env: py312-mode_mitogen-distro_debian9 - - name: Mito_312_debian10 - python_version: '3.12' - tox_env: py312-mode_mitogen-distro_debian10 - - name: Mito_312_debian11 - python_version: '3.12' - tox_env: py312-mode_mitogen-distro_debian11 - - name: Mito_312_ubuntu1604 - python_version: '3.12' - tox_env: py312-mode_mitogen-distro_ubuntu1604 - - name: Mito_312_ubuntu1804 - python_version: '3.12' - tox_env: py312-mode_mitogen-distro_ubuntu1804 - - name: Mito_312_ubuntu2004 - python_version: '3.12' - tox_env: py312-mode_mitogen-distro_ubuntu2004 steps: - uses: actions/checkout@v4 @@ -225,23 +115,16 @@ jobs: # https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md runs-on: macos-12 timeout-minutes: 120 + env: + ANSIBLE_VERBOSITY: 3 strategy: fail-fast: false matrix: include: - - name: Mito_27 - tox_env: py27-mode_mitogen - - name: Mito_312 - tox_env: py312-mode_mitogen - - - name: Loc_27_210 - tox_env: py27-mode_localhost-ansible2.10 - name: Loc_312_10 tox_env: py312-mode_localhost-ansible10 - - name: Van_27_210 - tox_env: py27-mode_localhost-ansible2.10-strategy_linear - name: Van_312_10 tox_env: py312-mode_localhost-ansible10-strategy_linear diff --git a/ansible_mitogen/connection.py b/ansible_mitogen/connection.py index a3f66eacc..5ba6a23c8 100644 --- a/ansible_mitogen/connection.py +++ b/ansible_mitogen/connection.py @@ -642,11 +642,16 @@ def _fetch_task_var(task_vars, key): if '{' in str(val) and key in SPECIAL_TASK_VARS: # template every time rather than storing in a cache # in case a different template value is used in a different task - val = self.templar.template( - val, - preserve_trailing_newlines=True, - escape_backslashes=False - ) + try: + val = self.templar.template( + val, + preserve_trailing_newlines=True, + escape_backslashes=False + ) + except AttributeError: + LOG.error('self.templar=%r, type is %r', + self.templar, type(self.templar)) + raise return val task_vars = self._get_task_vars() @@ -882,6 +887,7 @@ def close(self): Ansible connection plugin method. """ + LOG.debug('%r: closing connection', self) self._put_connection() if self.binding: self.binding.close() @@ -900,6 +906,7 @@ def reset(self): Ansible connection plugin method. """ + LOG.debug('%r: resetting connection', self) if self._play_context.remote_addr is None: # <2.5.6 incorrectly populate PlayContext for reset_connection # https://github.com/ansible/ansible/issues/27520 diff --git a/ansible_mitogen/mixins.py b/ansible_mitogen/mixins.py index 38f351eda..929c6f856 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 @@ -452,13 +450,17 @@ def _low_level_execute_command(self, cmd, sudoable=True, in_data=None, Override the base implementation by simply calling target.exec_command() in the target context. """ - LOG.debug('_low_level_execute_command(%r, in_data=%r, exe=%r, dir=%r)', - cmd, type(in_data), executable, chdir) + LOG.debug( + '_low_level_execute_command(cmd=%r, sudoable=%r, in_data is %r, executable=%r, encoding_errors=%r, chdir=%r)', + cmd, sudoable, type(in_data), executable, encoding_errors, chdir, + ) if executable is None: # executable defaults to False executable = self._play_context.executable + LOG.debug('_low_level_execute_command() executable -> %r', executable) if executable: cmd = executable + ' -c ' + shlex_quote(cmd) + LOG.debug('_low_level_execute_command() cmd -> %r', cmd) # TODO: HACK: if finding python interpreter then we need to keep # calling exec_command until we run into the right python we'll use @@ -482,6 +484,7 @@ def _low_level_execute_command(self, cmd, sudoable=True, in_data=None, possible_pythons = ['python'] def _run_cmd(): + LOG.debug('_low_level_execute_command()._run_cmd(): using %r', self._connection) return self._connection.exec_command( cmd=cmd, in_data=in_data, @@ -491,10 +494,13 @@ def _run_cmd(): for possible_python in possible_pythons: try: + LOG.debug('_low_level_execute_command(): trying %s', possible_python) self._possible_python_interpreter = possible_python rc, stdout, stderr = _run_cmd() + LOG.debug('_low_level_execute_command(): got rc=%d, stdout=%r, stderr=%r', rc, stdout, stderr) # TODO: what exception is thrown? - except: + except BaseException as exc: + LOG.debug('%r._low_level_execute_command for possible_python=%r: %s, %r', self, possible_python, type(exc), exc) # we've reached the last python attempted and failed # TODO: could use enumerate(), need to check which version of python first had it though if possible_python == 'python': @@ -503,10 +509,12 @@ def _run_cmd(): continue stdout_text = to_text(stdout, errors=encoding_errors) + stderr_text = to_text(stderr, errors=encoding_errors) return { 'rc': rc, 'stdout': stdout_text, 'stdout_lines': stdout_text.splitlines(), - 'stderr': stderr, + 'stderr': stderr_text, + 'stderr_lines': stderr_text.splitlines(), } 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..197d6e5cf 100644 --- a/ansible_mitogen/plugins/connection/mitogen_ssh.py +++ b/ansible_mitogen/plugins/connection/mitogen_ssh.py @@ -32,56 +32,35 @@ import os.path import sys +from ansible.plugins.connection.ssh import ( + Connection as _ansible_ssh_Connection, + DOCUMENTATION as _ansible_ssh_DOCUMENTATION, +) + 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/strategy.py b/ansible_mitogen/strategy.py index 0a98e3162..345705e67 100644 --- a/ansible_mitogen/strategy.py +++ b/ansible_mitogen/strategy.py @@ -30,6 +30,7 @@ __metaclass__ = type import os +import logging import signal import threading @@ -48,6 +49,9 @@ import ansible.utils.sentinel +LOG = logging.getLogger(__name__) + + def _patch_awx_callback(): """ issue #400: AWX loads a display callback that suffers from thread-safety @@ -326,3 +330,7 @@ def run(self, iterator, play_context, result=0): self._worker_model.on_strategy_complete() finally: ansible_mitogen.process.set_worker_model(None) + + def _execute_meta(self, task, play_context, iterator, target_host): + LOG.debug('%r._execute_meta(task=%r, play_context=%r, iterator=%r, target_host=%r):', self, task, play_context, iterator, target_host) + return super(StrategyMixin, self)._execute_meta(task, play_context, iterator, target_host) diff --git a/ansible_mitogen/transport_config.py b/ansible_mitogen/transport_config.py index 39df3f6a6..ffa4438da 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,31 @@ def __init__(self, connection, play_context, transport, inventory_name): # used to run interpreter discovery self._action = connection._action + # Better to use ActionBase.get_become_option? + 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) + + # Better to use ActionBase.get_plugin_option? + 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 +457,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 +484,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 +503,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/all.yml b/tests/ansible/all.yml index 7a3e70001..e3034fa30 100644 --- a/tests/ansible/all.yml +++ b/tests/ansible/all.yml @@ -1,6 +1,6 @@ - import_playbook: setup/all.yml tags: setup -- import_playbook: regression/all.yml - tags: regression +#- import_playbook: regression/all.yml +# tags: regression - import_playbook: integration/all.yml tags: integration diff --git a/tests/ansible/ansible.cfg b/tests/ansible/ansible.cfg index 537b50590..ddf5a0070 100644 --- a/tests/ansible/ansible.cfg +++ b/tests/ansible/ansible.cfg @@ -47,6 +47,7 @@ host_key_checking = False [inventory] any_unparsed_is_failed = true +enable_plugins = ini host_pattern_mismatch = error [callback_profile_tasks] diff --git a/tests/ansible/hosts/default.hosts b/tests/ansible/hosts/default.hosts index adc271e24..571f5d435 100644 --- a/tests/ansible/hosts/default.hosts +++ b/tests/ansible/hosts/default.hosts @@ -19,3 +19,18 @@ ssh-common-args ansible_host=localhost ansible_user="{{ lookup('pipe', 'whoami') [issue905:vars] ansible_ssh_common_args=-o PermitLocalCommand=yes -o LocalCommand="touch {{ ssh_args_canary_file }}" ssh_args_canary_file=/tmp/ssh_args_{{ inventory_hostname }} + +[tt_targets_bare] +tt-bare + +[tt_targets_bare:vars] +ansible_host=localhost +ansible_user=mitogen__has_sudo_nopw + +[tt_targets_inventory] +tt-password ansible_password="{{ 'has_sudo_nopw_password' | trim }}" ansible_port=22 +tt-port ansible_password=has_sudo_nopw_password ansible_port="{{ 22 | int }}" + +[tt_targets_inventory:vars] +ansible_host=localhost +ansible_user=mitogen__has_sudo_nopw diff --git a/tests/ansible/hosts/k3.hosts b/tests/ansible/hosts/k3.hosts.disabled similarity index 100% rename from tests/ansible/hosts/k3.hosts rename to tests/ansible/hosts/k3.hosts.disabled diff --git a/tests/ansible/hosts/localhost.hosts b/tests/ansible/hosts/localhost.hosts.disabled similarity index 100% rename from tests/ansible/hosts/localhost.hosts rename to tests/ansible/hosts/localhost.hosts.disabled diff --git a/tests/ansible/integration/all.yml b/tests/ansible/integration/all.yml index ac196584e..97246dbad 100644 --- a/tests/ansible/integration/all.yml +++ b/tests/ansible/integration/all.yml @@ -3,39 +3,39 @@ # This playbook imports all tests that are known to work at present. # -- import_playbook: action/all.yml - tags: action -- import_playbook: async/all.yml - tags: async -- import_playbook: become/all.yml - tags: become -- import_playbook: connection/all.yml - tags: connection -- import_playbook: connection_delegation/all.yml - tags: connection_delegation -- import_playbook: connection_loader/all.yml - tags: connection_loader -- import_playbook: context_service/all.yml - tags: context_service -- import_playbook: glibc_caches/all.yml - tags: glibc_caches -- import_playbook: interpreter_discovery/all.yml - tags: interpreter_discovery -- import_playbook: local/all.yml - tags: local -- import_playbook: module_utils/all.yml - tags: module_utils -- import_playbook: playbook_semantics/all.yml - tags: playbook_semantics -- import_playbook: process/all.yml - tags: process -- import_playbook: runner/all.yml - tags: runner +# - import_playbook: action/all.yml +# tags: action +# - import_playbook: async/all.yml +# tags: async +# - import_playbook: become/all.yml +# tags: become +# - import_playbook: connection/all.yml +# tags: connection +# - import_playbook: connection_delegation/all.yml +# tags: connection_delegation +# - import_playbook: connection_loader/all.yml +# tags: connection_loader +# - import_playbook: context_service/all.yml +# tags: context_service +# - import_playbook: glibc_caches/all.yml +# tags: glibc_caches +# - import_playbook: interpreter_discovery/all.yml +# tags: interpreter_discovery +# - import_playbook: local/all.yml +# tags: local +# - import_playbook: module_utils/all.yml +# tags: module_utils +# - import_playbook: playbook_semantics/all.yml +# tags: playbook_semantics +# - import_playbook: process/all.yml +# tags: process +# - import_playbook: runner/all.yml +# tags: runner - import_playbook: ssh/all.yml tags: ssh -- import_playbook: strategy/all.yml - tags: strategy -- import_playbook: stub_connections/all.yml - tags: stub_connections -- import_playbook: transport_config/all.yml - tags: transport_config +# - import_playbook: strategy/all.yml +# tags: strategy +# - import_playbook: stub_connections/all.yml +# tags: stub_connections +# - import_playbook: transport_config/all.yml +# tags: transport_config 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/ssh/all.yml b/tests/ansible/integration/ssh/all.yml index 5c16f187b..18e768a92 100644 --- a/tests/ansible/integration/ssh/all.yml +++ b/tests/ansible/integration/ssh/all.yml @@ -1,5 +1,8 @@ -- import_playbook: args.yml -- import_playbook: config.yml -- import_playbook: password.yml -- import_playbook: timeouts.yml -- import_playbook: variables.yml +#- import_playbook: args.yml +#- import_playbook: config.yml +#- import_playbook: password.yml +#- import_playbook: timeouts.yml +#- import_playbook: templated_by_inv.yml +#- import_playbook: templated_by_keyword.yml +- import_playbook: templated_by_taskvar.yml +#- import_playbook: variables.yml diff --git a/tests/ansible/integration/ssh/templated_by_inv.yml b/tests/ansible/integration/ssh/templated_by_inv.yml new file mode 100644 index 000000000..686518fd4 --- /dev/null +++ b/tests/ansible/integration/ssh/templated_by_inv.yml @@ -0,0 +1,7 @@ +- name: integration/ssh/templated_by_inv.yml + hosts: tt_targets_inventory + gather_facts: false + tasks: + - meta: reset_connection + - name: Templated variables in inventory + ping: diff --git a/tests/ansible/integration/ssh/templated_by_keyword.yml b/tests/ansible/integration/ssh/templated_by_keyword.yml new file mode 100644 index 000000000..975c6b98d --- /dev/null +++ b/tests/ansible/integration/ssh/templated_by_keyword.yml @@ -0,0 +1,16 @@ +- name: integration/ssh/templated_by_keyword.yml + hosts: tt_targets_bare + gather_facts: false + tasks: + - meta: reset_connection + - name: Templated variables in playbook keywords + #become: + #become_exe: + #become_flags: + #become_method: + #become_user: + port: "{{ hostvars[groups['test-targets'][0]].ansible_port | default(22) }}" + #remote_user: + vars: + ansible_password: has_sudo_nopw_password + ping: diff --git a/tests/ansible/integration/ssh/templated_by_taskvar.yml b/tests/ansible/integration/ssh/templated_by_taskvar.yml new file mode 100644 index 000000000..59a1d41f8 --- /dev/null +++ b/tests/ansible/integration/ssh/templated_by_taskvar.yml @@ -0,0 +1,10 @@ +- name: integration/ssh/templated_by_taskvar.yml + hosts: tt_targets_bare + gather_facts: false + tasks: + - meta: reset_connection + - name: Templated variables in task + vars: + ansible_port: "{{ hostvars[groups['test-targets'][0]].ansible_port | default(22) }}" + ansible_password: has_sudo_nopw_password + ping: 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/tests/ansible/templates/test-targets.j2 b/tests/ansible/templates/test-targets.j2 index e27081926..a6bd59fa1 100644 --- a/tests/ansible/templates/test-targets.j2 +++ b/tests/ansible/templates/test-targets.j2 @@ -37,3 +37,34 @@ ansible_user=mitogen__has_sudo_nopw ansible_password=has_sudo_nopw_password ansible_ssh_common_args=-o PermitLocalCommand=yes -o LocalCommand="touch {{ '{{' }} ssh_args_canary_file {{ '}}' }}" ssh_args_canary_file=/tmp/ssh_args_{{ '{{' }} inventory_hostname {{ '}}' }} + +{% set tt = containers[0] %} + +[tt_targets_bare] +tt-bare + +[tt_targets_bare:vars] +ansible_python_interpreter={{ tt.python_path }} +ansible_host={{ tt.hostname }} +ansible_user=mitogen__has_sudo_nopw + +#[tt_become_exe] +#[tt_become_flags] +#[tt_become_pass] +#[tt_become_user] + +[tt_host_key_checking] +[tt_private_key_file] +#[tt_python_path] +[tt_ssh_args] +[tt_ssh_executable] +[tt_timeout] + +[tt_targets_inventory] +tt-password ansible_password="{{ '{{' }} 'has_sudo_nopw_password' | trim {{ '}}' }}" ansible_port={{ tt.port }} +tt-port ansible_password=has_sudo_nopw_password ansible_port="{{ '{{' }} {{ tt.port }} | int {{ '}}' }}" + +[tt_targets_inventory:vars] +ansible_python_interpreter={{ tt.python_path }} +ansible_host={{ tt.hostname }} +ansible_user=mitogen__has_sudo_nopw 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 # ==== ========== ========== ========== ========== ========== ========== ========== ========== ========== ==========