diff --git a/.ci/install_sshpass b/.ci/install_sshpass
new file mode 100755
index 00000000..05d7ebd6
--- /dev/null
+++ b/.ci/install_sshpass
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+
+set -o errexit
+set -o nounset
+set -o pipefail
+
+VERSION="$1"
+
+curl \
+ --fail \
+ --location \
+ --no-progress-meter \
+ --remote-name \
+ "https://downloads.sourceforge.net/project/sshpass/sshpass/${VERSION}/sshpass-${VERSION}.tar.gz"
+tar xvf "sshpass-${VERSION}.tar.gz"
+cd "sshpass-${VERSION}"
+./configure
+sudo make install
diff --git a/.ci/localhost_ansible_tests.py b/.ci/localhost_ansible_tests.py
index 359dc195..9203f120 100755
--- a/.ci/localhost_ansible_tests.py
+++ b/.ci/localhost_ansible_tests.py
@@ -17,17 +17,6 @@ with ci_lib.Fold('unit_tests'):
with ci_lib.Fold('job_setup'):
os.chmod(ci_lib.TESTS_SSH_PRIVATE_KEY_FILE, int('0600', 8))
- # NOTE: sshpass v1.06 causes errors so pegging to 1.05 -> "msg": "Error when changing password","out": "passwd: DS error: eDSAuthFailed\n",
- # there's a checksum error with "brew install http://git.io/sshpass.rb" though, so installing manually
- if not ci_lib.exists_in_path('sshpass'):
- subprocess.check_call(
- "curl -O -L https://sourceforge.net/projects/sshpass/files/sshpass/1.05/sshpass-1.05.tar.gz && \
- tar xvf sshpass-1.05.tar.gz && \
- cd sshpass-1.05 && \
- ./configure && \
- sudo make install",
- shell=True,
- )
with ci_lib.Fold('machine_prep'):
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 6f6a6871..ac00d84b 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -177,10 +177,16 @@ jobs:
- name: Ans_313_11
python_version: '3.13'
tox_env: py313-mode_ansible-ansible11
+ - name: Ans_313_12
+ python_version: '3.13'
+ tox_env: py313-mode_ansible-ansible12
- name: Van_313_11
python_version: '3.13'
tox_env: py313-mode_ansible-ansible11-strategy_linear
+ - name: Van_313_12
+ python_version: '3.13'
+ tox_env: py313-mode_ansible-ansible12-strategy_linear
- name: Mito_313
python_version: '3.13'
@@ -268,11 +274,19 @@ jobs:
tox_env: py313-mode_mitogen
- name: Loc_313_11
+ sshpass_version: "1.10"
tox_env: py313-mode_localhost-ansible11
- name: Van_313_11
+ sshpass_version: "1.10"
tox_env: py313-mode_localhost-ansible11-strategy_linear
+ - name: Loc_313_12
+ tox_env: py313-mode_localhost-ansible12
+
+ - name: Van_313_12
+ tox_env: py313-mode_localhost-ansible12-strategy_linear
+
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
@@ -305,6 +319,8 @@ jobs:
# GitHub macOS 12 images: python2.7 is installed, but not on $PATH
echo "/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7: sys.executable: $(/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7 -c 'import sys; print(sys.executable)')"
fi
+ - run: .ci/install_sshpass ${{ matrix.sshpass_version }}
+ if: ${{ matrix.sshpass_version }}
- name: Install tooling
run: |
set -o errexit -o nounset -o pipefail
diff --git a/.gitignore b/.gitignore
index 7297d720..43e46a19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.ansible/
.coverage
.tox
.venv
diff --git a/ansible_mitogen/connection.py b/ansible_mitogen/connection.py
index 5053a5f5..03856f4d 100644
--- a/ansible_mitogen/connection.py
+++ b/ansible_mitogen/connection.py
@@ -147,7 +147,7 @@ def _connect_ssh(spec):
'ssh_path': spec.ssh_executable(),
'connect_timeout': spec.timeout(),
'ssh_args': spec.ssh_args(),
- 'ssh_debug_level': spec.mitogen_ssh_debug_level(),
+ 'ssh_debug_level': spec.verbosity(),
'remote_name': get_remote_name(spec),
'keepalive_count': (
spec.mitogen_ssh_keepalive_count() or 10
@@ -767,7 +767,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
C.BECOME_ALLOW_SAME_USER):
stack += (CONNECTION_METHOD[spec.become_method()](spec),)
- return stack
+ return ansible_mitogen.utils.unsafe.cast(stack)
def _build_stack(self):
"""
diff --git a/ansible_mitogen/loaders.py b/ansible_mitogen/loaders.py
index 123dd4ac..9597e3ee 100644
--- a/ansible_mitogen/loaders.py
+++ b/ansible_mitogen/loaders.py
@@ -49,7 +49,7 @@ __all__ = [
ANSIBLE_VERSION_MIN = (2, 10)
-ANSIBLE_VERSION_MAX = (2, 18)
+ANSIBLE_VERSION_MAX = (2, 19)
NEW_VERSION_MSG = (
"Your Ansible version (%s) is too recent. The most recent version\n"
diff --git a/ansible_mitogen/mixins.py b/ansible_mitogen/mixins.py
index dadf2c17..8ddbb437 100644
--- a/ansible_mitogen/mixins.py
+++ b/ansible_mitogen/mixins.py
@@ -29,6 +29,7 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
+import json
import logging
import os
import pwd
@@ -42,7 +43,6 @@ import ansible.vars.clean
from ansible.module_utils.common.text.converters import to_bytes, to_text
from ansible.module_utils.six.moves import shlex_quote
-from ansible.parsing.utils.jsonify import jsonify
import mitogen.core
import mitogen.select
@@ -219,8 +219,13 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
Used by the base _execute_module(), and in <2.4 also by the template
action module, and probably others.
"""
+ if data is None and ansible_mitogen.utils.ansible_version[:2] <= (2, 18):
+ data = '{}'
if isinstance(data, dict):
- data = jsonify(data)
+ try:
+ data = json.dumps(data, ensure_ascii=False)
+ except UnicodeDecodeError:
+ data = json.dumps(data)
if not isinstance(data, bytes):
data = to_bytes(data, errors='surrogate_or_strict')
@@ -402,15 +407,17 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
if not self._mitogen_rediscovered_interpreter:
result['ansible_facts'][self._discovered_interpreter_key] = self._discovered_interpreter
- if self._discovery_warnings:
+ discovery_warnings = getattr(self, '_discovery_warnings', [])
+ if discovery_warnings:
if result.get('warnings') is None:
result['warnings'] = []
- result['warnings'].extend(self._discovery_warnings)
+ result['warnings'].extend(discovery_warnings)
- if self._discovery_deprecation_warnings:
+ discovery_deprecation_warnings = getattr(self, '_discovery_deprecation_warnings', [])
+ if discovery_deprecation_warnings:
if result.get('deprecations') is None:
result['deprecations'] = []
- result['deprecations'].extend(self._discovery_deprecation_warnings)
+ result['deprecations'].extend(discovery_deprecation_warnings)
return ansible.utils.unsafe_proxy.wrap_var(result)
@@ -429,7 +436,10 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
"stderr": "stderr data"
}
"""
- data = self._parse_returned_data(result)
+ if ansible_mitogen.utils.ansible_version[:2] >= (2, 19):
+ data = self._parse_returned_data(result, profile='legacy')
+ else:
+ data = self._parse_returned_data(result)
# Cutpasted from the base implementation.
if 'stdout' in data and 'stdout_lines' not in data:
diff --git a/ansible_mitogen/planner.py b/ansible_mitogen/planner.py
index 2915f4b7..5d0fbd78 100644
--- a/ansible_mitogen/planner.py
+++ b/ansible_mitogen/planner.py
@@ -170,6 +170,7 @@ class Planner(object):
"""
binding = self._inv.connection.get_binding()
+ kwargs = ansible_mitogen.utils.unsafe.cast(kwargs)
new = dict((mitogen.core.UnicodeType(k), kwargs[k])
for k in kwargs)
new.setdefault('good_temp_dir',
@@ -204,7 +205,7 @@ class BinaryPlanner(Planner):
module=self._inv.module_name,
path=self._inv.module_path,
json_args=json.dumps(self._inv.module_args),
- env=self._inv.env,
+ env=ansible_mitogen.utils.unsafe.cast(self._inv.env),
**kwargs
)
@@ -546,7 +547,7 @@ def _invoke_async_task(invocation, planner):
call_recv = context.call_async(
ansible_mitogen.target.run_module_async,
job_id=job_id,
- timeout_secs=invocation.timeout_secs,
+ timeout_secs=ansible_mitogen.utils.unsafe.cast(invocation.timeout_secs),
started_sender=started_recv.to_sender(),
kwargs=planner.get_kwargs(),
)
diff --git a/ansible_mitogen/runner.py b/ansible_mitogen/runner.py
index b60e537c..ce7dceb9 100644
--- a/ansible_mitogen/runner.py
+++ b/ansible_mitogen/runner.py
@@ -73,6 +73,7 @@ except ImportError:
from io import StringIO
# Prevent accidental import of an Ansible module from hanging on stdin read.
+# FIXME Should probably be b'{}' or None. Ansible 2.19 has bytes | None = None.
import ansible.module_utils.basic
ansible.module_utils.basic._ANSIBLE_ARGS = '{}'
@@ -635,6 +636,7 @@ class NewStyleStdio(object):
sys.stderr = StringIO()
encoded = json.dumps({'ANSIBLE_MODULE_ARGS': args})
ansible.module_utils.basic._ANSIBLE_ARGS = utf8(encoded)
+ ansible.module_utils.basic._ANSIBLE_PROFILE = 'legacy'
sys.stdin = StringIO(mitogen.core.to_text(encoded))
self.original_get_path = getattr(ansible.module_utils.basic,
@@ -649,7 +651,9 @@ class NewStyleStdio(object):
sys.stdout = self.original_stdout
sys.stderr = self.original_stderr
sys.stdin = self.original_stdin
+ # FIXME Should probably be b'{}' or None. Ansible 2.19 has bytes | None = None.
ansible.module_utils.basic._ANSIBLE_ARGS = '{}'
+ ansible.module_utils.basic._ANSIBLE_PROFILE = None
class ProgramRunner(Runner):
diff --git a/ansible_mitogen/services.py b/ansible_mitogen/services.py
index abc0e379..a48ab757 100644
--- a/ansible_mitogen/services.py
+++ b/ansible_mitogen/services.py
@@ -57,6 +57,7 @@ import mitogen.service
import ansible_mitogen.loaders
import ansible_mitogen.module_finder
import ansible_mitogen.target
+import ansible_mitogen.utils
import ansible_mitogen.utils.unsafe
@@ -338,7 +339,12 @@ class ContextService(mitogen.service.Service):
'ansible_mitogen.target',
'mitogen.fork',
'mitogen.service',
- )
+ ) + ((
+ 'ansible.module_utils._internal._json._profiles._module_legacy_c2m',
+ 'ansible.module_utils._internal._json._profiles._module_legacy_m2c',
+ 'ansible.module_utils._internal._json._profiles._module_modern_c2m',
+ 'ansible.module_utils._internal._json._profiles._module_legacy_m2c',
+ ) if ansible_mitogen.utils.ansible_version[:2] >= (2, 19) else ())
def _send_module_forwards(self, context):
if hasattr(self.router.responder, 'forward_modules'):
diff --git a/ansible_mitogen/transport_config.py b/ansible_mitogen/transport_config.py
index 22afd197..3d5fac3c 100644
--- a/ansible_mitogen/transport_config.py
+++ b/ansible_mitogen/transport_config.py
@@ -406,6 +406,12 @@ class Spec(with_metaclass(abc.ABCMeta, object)):
Value of "ansible_doas_exe" variable.
"""
+ @abc.abstractmethod
+ def verbosity(self):
+ """
+ How verbose to make logging or diagnostics output.
+ """
+
class PlayContextSpec(Spec):
"""
@@ -601,6 +607,17 @@ class PlayContextSpec(Spec):
os.environ.get('ANSIBLE_DOAS_EXE')
)
+ def verbosity(self):
+ try:
+ verbosity = self._connection.get_option('verbosity', hostvars=self._task_vars)
+ except KeyError:
+ verbosity = self.mitogen_ssh_debug_level()
+
+ if verbosity:
+ return int(verbosity)
+
+ return 0
+
class MitogenViaSpec(Spec):
"""
@@ -836,3 +853,13 @@ class MitogenViaSpec(Spec):
self._host_vars.get('ansible_doas_exe') or
os.environ.get('ANSIBLE_DOAS_EXE')
)
+
+ def verbosity(self):
+ verbosity = self._host_vars.get('ansible_ssh_verbosity')
+ if verbosity is None:
+ verbosity = self.mitogen_ssh_debug_level()
+
+ if verbosity:
+ return int(verbosity)
+
+ return 0
diff --git a/ansible_mitogen/utils/unsafe.py b/ansible_mitogen/utils/unsafe.py
index b2c3d533..a3aed462 100644
--- a/ansible_mitogen/utils/unsafe.py
+++ b/ansible_mitogen/utils/unsafe.py
@@ -16,8 +16,11 @@ __all__ = [
def _cast_to_dict(obj): return {cast(k): cast(v) for k, v in obj.items()}
def _cast_to_list(obj): return [cast(v) for v in obj]
+def _cast_to_set(obj): return set(cast(v) for v in obj)
+def _cast_to_tuple(obj): return tuple(cast(v) for v in obj)
def _cast_unsafe(obj): return obj._strip_unsafe()
def _passthrough(obj): return obj
+def _untag(obj): return obj._native_copy()
# A dispatch table to cast objects based on their exact type.
@@ -26,25 +29,64 @@ _CAST_DISPATCH = {
bytes: bytes,
dict: _cast_to_dict,
list: _cast_to_list,
- tuple: _cast_to_list,
mitogen.core.UnicodeType: mitogen.core.UnicodeType,
}
_CAST_DISPATCH.update({t: _passthrough for t in mitogen.utils.PASSTHROUGH})
-if hasattr(ansible.utils.unsafe_proxy.AnsibleUnsafeText, '_strip_unsafe'):
+_CAST_SUBTYPES = [
+ dict,
+ list,
+]
+
+if hasattr(ansible.utils.unsafe_proxy, 'TrustedAsTemplate'):
+ import datetime
+ import ansible.module_utils._internal._datatag
+ _CAST_DISPATCH.update({
+ set: _cast_to_set,
+ tuple: _cast_to_tuple,
+ ansible.module_utils._internal._datatag._AnsibleTaggedBytes: _untag,
+ ansible.module_utils._internal._datatag._AnsibleTaggedDate: _untag,
+ ansible.module_utils._internal._datatag._AnsibleTaggedDateTime: _untag,
+ ansible.module_utils._internal._datatag._AnsibleTaggedDict: _cast_to_dict,
+ ansible.module_utils._internal._datatag._AnsibleTaggedFloat: _untag,
+ ansible.module_utils._internal._datatag._AnsibleTaggedInt: _untag,
+ ansible.module_utils._internal._datatag._AnsibleTaggedList: _cast_to_list,
+ ansible.module_utils._internal._datatag._AnsibleTaggedSet: _cast_to_set,
+ ansible.module_utils._internal._datatag._AnsibleTaggedStr: _untag,
+ ansible.module_utils._internal._datatag._AnsibleTaggedTime: _untag,
+ ansible.module_utils._internal._datatag._AnsibleTaggedTuple: _cast_to_tuple,
+ ansible.utils.unsafe_proxy.AnsibleUnsafeBytes: bytes,
+ ansible.utils.unsafe_proxy.AnsibleUnsafeText: mitogen.core.UnicodeType,
+ datetime.date: _passthrough,
+ datetime.datetime: _passthrough,
+ datetime.time: _passthrough,
+ })
+ _CAST_SUBTYPES.extend([
+ set,
+ tuple,
+ ])
+elif hasattr(ansible.utils.unsafe_proxy.AnsibleUnsafeText, '_strip_unsafe'):
_CAST_DISPATCH.update({
+ tuple: _cast_to_list,
ansible.utils.unsafe_proxy.AnsibleUnsafeBytes: _cast_unsafe,
ansible.utils.unsafe_proxy.AnsibleUnsafeText: _cast_unsafe,
ansible.utils.unsafe_proxy.NativeJinjaUnsafeText: _cast_unsafe,
})
+ _CAST_SUBTYPES.extend([
+ tuple,
+ ])
elif ansible_mitogen.utils.ansible_version[:2] <= (2, 16):
_CAST_DISPATCH.update({
+ tuple: _cast_to_list,
ansible.utils.unsafe_proxy.AnsibleUnsafeBytes: bytes,
ansible.utils.unsafe_proxy.AnsibleUnsafeText: mitogen.core.UnicodeType,
})
+ _CAST_SUBTYPES.extend([
+ tuple,
+ ])
else:
mitogen_ver = '.'.join(str(v) for v in mitogen.__version__)
- raise ImportError("Mitogen %s can't unwrap Ansible %s AnsibleUnsafe objects"
+ raise ImportError("Mitogen %s can't cast Ansible %s objects"
% (mitogen_ver, ansible.__version__))
@@ -73,7 +115,9 @@ def cast(obj):
return unwrapper(obj)
# Slow path: obj is some unknown subclass
- if isinstance(obj, dict): return _cast_to_dict(obj)
- if isinstance(obj, (list, tuple)): return _cast_to_list(obj)
+ for typ_ in _CAST_SUBTYPES:
+ if isinstance(obj, typ_):
+ unwrapper = _CAST_DISPATCH[typ_]
+ return unwrapper(obj)
return mitogen.utils.cast(obj)
diff --git a/docs/ansible_detailed.rst b/docs/ansible_detailed.rst
index 3d80a290..06aa4955 100644
--- a/docs/ansible_detailed.rst
+++ b/docs/ansible_detailed.rst
@@ -141,7 +141,9 @@ Noteworthy Differences
+-----------------+ 3.10 - 3.13 |
| 10 | |
+-----------------+-----------------+
- | 11 | 3.11 - 3.13 |
+ | 11 | |
+ +-----------------+ 3.11 - 3.13+ |
+ | 12 | |
+-----------------+-----------------+
Verify your installation is running one of these versions by checking
@@ -1016,6 +1018,8 @@ Like the :ans:conn:`ssh` except connection delegation is supported.
* ``ansible_ssh_private_key_file``
* ``ansible_ssh_pass``, ``ansible_ssh_password``, ``ansible_password``
(default: assume passwordless)
+* ``ansible_ssh_host_key_checking``, ``ansible_host_key_checking`` (default:
+ :data:`True`)
* ``ssh_args``, ``ssh_common_args``, ``ssh_extra_args``
* ``mitogen_mask_remote_name``: if :data:`True`, mask the identity of the
Ansible controller process on remote machines. To simplify diagnostics,
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 4e3f703e..61b29bd4 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -18,6 +18,59 @@ To avail of fixes in an unreleased version, please download a ZIP file
`directly from GitHub `_.
+v0.3.25 (2025-07-29)
+--------------------
+
+Ansible 12 has deprecated third-party strategy plugins. This is currently
+how Mitogen integrates with Ansible (e.g. `ANSIBLE_STRATEGY=mitogen_linear`).
+Running Ansible 12 + Mitogen will currently print a deprecation warning
+
+ [DEPRECATION WARNING]: Use of strategy plugins not included in
+ ansible.builtin are deprecated [...]. This feature will be removed from
+ ansible-core in a future release.
+
+Ansible + Mitogen will still work for now. Mitogen is considering alternatives
+to strategy plugins under :gh:issue:`1278`.
+
+* :gh:issue:`1258` Ansible 12 (ansible-core 2.19) support
+
+
+v0.3.25b1 (2025-07-21)
+----------------------
+
+* :gh:issue:`1303` CI: Switch to archived Debian 10 (buster) apt repository
+
+
+v0.3.25a3 (2025-07-02)
+----------------------
+
+* :gh:issue:`1285` CI: use `result_format = yaml` for Ansible test output,
+ instead of deprecated `stdout_callback = yaml`
+* :gh:issue:`1293` CI: Fix ``ansible_version`` comparisons when an Ansible
+ release candidate is under test
+* :gh:issue:`1275` CI: Test ``ansible_ssh_password`` behaviour without
+ ``sshpass`` installed
+* :gh:issue:`1282` :mod:`ansible_mitogen`: Support ``ANSIBLE_SSH_VERBOSITY``
+ with Ansible 12
+
+
+v0.3.25a2 (2025-06-21)
+----------------------
+
+* :gh:issue:`1274` :mod:`ansible_mitogen`: Replace use of `jsonify()`, which
+ is deprecated form Ansible 12 (ansible-core 2.19)
+
+
+v0.3.25a1 (2025-06-05)
+----------------------
+
+* :gh:issue:`1258` Initial Ansible 12 (ansible-core 2.19) support
+* :gh:issue:`1258` :mod:`ansible_mitogen`: Initial Ansible datatag support
+ (:gh:anspull:`84621`)
+* :gh:issue:`1258` :mod:`ansible_mitogen`: Ansible 12 (ansible-core 2.19) test
+ jobs
+
+
v0.3.24 (2025-05-29)
--------------------
diff --git a/docs/conf.py b/docs/conf.py
index 9ad5b534..276a05d6 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -5,8 +5,11 @@ sys.path.append('.')
def changelog_version(path, encoding='utf-8'):
+ "Return the 1st *stable* (not pre, dev) version in the changelog"
+ # See also grep_version() in setup.py
+ # e.g. "0.1.2, (1999-12-31)\n"
version_pattern = re.compile(
- r'^v(?P[0-9]+\.[0-9]+\.[0-9]+)',
+ r'^v(?P\d+\.\d+\.\d+) \((?P\d\d\d\d-\d\d-\d\d)\)$',
re.MULTILINE,
)
@@ -64,6 +67,15 @@ domainrefs = {
'text': '#%s',
'url': 'https://github.com/mitogen-hq/mitogen/pull/%s',
},
+ 'gh:ansissue': {
+ 'text': 'Ansible #%s',
+ 'url': 'https://github.com/ansible/ansible/issues/%s',
+ },
+ 'gh:anspull': {
+ 'text': 'Ansible #%s',
+ 'url': 'https://github.com/ansible/ansible/pull/%s',
+ },
+
'ans:mod': {
'text': '%s module',
'url': 'https://docs.ansible.com/ansible/latest/modules/%s_module.html',
diff --git a/mitogen/__init__.py b/mitogen/__init__.py
index 3fe380fa..7133b66c 100644
--- a/mitogen/__init__.py
+++ b/mitogen/__init__.py
@@ -35,7 +35,7 @@ be expected. On the slave, it is built dynamically during startup.
#: Library version as a tuple.
-__version__ = (0, 3, 24)
+__version__ = (0, 3, 25)
#: This is :data:`False` in slave contexts. Previously it was used to prevent
diff --git a/setup.cfg b/setup.cfg
index 08919787..ded55039 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[bdist_wheel]
-universal=1
+python_tag = py2.py3
[coverage:run]
branch = true
diff --git a/setup.py b/setup.py
index 1639d383..f8aeffea 100644
--- a/setup.py
+++ b/setup.py
@@ -33,15 +33,34 @@ from setuptools import find_packages, setup
def grep_version():
+ # See also changlelog_version() in docs.conf.py
path = os.path.join(os.path.dirname(__file__), 'mitogen/__init__.py')
+
+ # Based on https://packaging.python.org/en/latest/specifications/version-specifiers/#appendix-parsing-version-strings-with-regular-expressions
+ # e.g. "__version__ = (0, 1, 2)", "__version__ = (0, 1, 3, 'dev')",
+ # "__version__ = (0, 1, 4, 'a', 1)"
version_pattern = re.compile(
- r"__version__ = \((\d+), (\d+), (\d+)(?:, '(dev)')?\)",
+ r'''
+ ^__version__\s=\s\(
+ (?P\d+)
+ ,\s
+ (?P\d+)
+ ,\s
+ (?P\d+)
+ (?:
+ (?:,\s '(?Pdev)')
+ | (?:,\s '(?Pa|b)' ,\s (?P\d+))
+ )?
+ \)
+ $
+ ''',
+ re.MULTILINE | re.VERBOSE,
)
with open(path) as fp:
match = version_pattern.search(fp.read())
if match is None:
raise ValueError('Could not find __version__ string in %s', path)
- # E.g. '0.1.2', '0.1.3dev'
+ # e.g. '0.1.2', '0.1.3dev', '0.1.4a1'
return '.'.join(str(part) for part in match.groups() if part)
diff --git a/tests/ansible/ansible.cfg b/tests/ansible/ansible.cfg
index 48a4eec3..4060d9ea 100644
--- a/tests/ansible/ansible.cfg
+++ b/tests/ansible/ansible.cfg
@@ -1,5 +1,7 @@
[defaults]
any_errors_fatal = true
+# Ansible >= 6 (ansible-core >= 2.13)
+callback_result_format = yaml
# callbacks_enabled was added in Ansible 4 (ansible-core 2.11).
# profile_tasks: Displays timing for each task and summary table of top N tasks
# timer: Displays "Playbook run took 0 days, 0 hours, ..."
@@ -17,13 +19,13 @@ strategy_plugins = ../../ansible_mitogen/plugins/strategy
inventory_plugins = lib/inventory
action_plugins = lib/action
callback_plugins = lib/callback
-stdout_callback = yaml
vars_plugins = lib/vars
library = lib/modules
filter_plugins = lib/filters
module_utils = lib/module_utils
retry_files_enabled = False
-show_task_path_on_failure = true # Added in ansible-core 2.11
+# Ansible >= 4 (ansible-core >= 2.11)
+show_task_path_on_failure = true
display_args_to_stdout = True
forks = 100
diff --git a/tests/ansible/hosts/group_vars/all.yml b/tests/ansible/hosts/group_vars/all.yml
index 6f518cad..7ec10d6a 100644
--- a/tests/ansible/hosts/group_vars/all.yml
+++ b/tests/ansible/hosts/group_vars/all.yml
@@ -1,4 +1,11 @@
---
+# Avoid `ansible_version.full is version(..., strict=True)` limitations.
+# Pre-release info (alpha/beta/rc) is intentionally ignored.
+# Behaviour that is present or expected in ansible-core 2.50.x should be
+# tested even if ansible-core 2.50.0a1 or 2.50.0rc1 is under test.
+ansible_version_major_minor: "{{ ansible_version.major }}.{{ ansible_version.minor }}"
+ansible_version_major_minor_patch: "{{ ansible_version.major }}.{{ ansible_version.minor }}.{{ ansible_version.revision | regex_search('^[0-9]+') }}"
+
become_unpriv_available: >-
{#
Vanilla Ansible >= 4 (ansible-core >= 2.11) can use `setfacl` for
@@ -13,7 +20,7 @@ become_unpriv_available: >-
(
not is_mitogen
and is_macos_controller
- and ansible_version.full is version("2.11", ">=", strict=True)
+ and ansible_version_major_minor is version("2.11", ">=", strict=True)
)
or (
is_mitogen
@@ -22,7 +29,7 @@ become_unpriv_available: >-
or (
is_mitogen
and ansible_python_interpreter is not defined
- and ansible_version.full is version("2.18", "<", strict=True)
+ and ansible_version_major_minor is version("2.18", "<", strict=True)
)
-}}
diff --git a/tests/ansible/hosts/group_vars/debian10.yml b/tests/ansible/hosts/group_vars/debian10.yml
new file mode 100644
index 00000000..c6c0a268
--- /dev/null
+++ b/tests/ansible/hosts/group_vars/debian10.yml
@@ -0,0 +1,4 @@
+package_manager_repos:
+ - dest: /etc/apt/sources.list
+ content: |
+ deb http://archive.debian.org/debian buster main contrib non-free
diff --git a/tests/ansible/hosts/group_vars/debian11.yml b/tests/ansible/hosts/group_vars/debian11.yml
index 9f62f43c..30c98341 100644
--- a/tests/ansible/hosts/group_vars/debian11.yml
+++ b/tests/ansible/hosts/group_vars/debian11.yml
@@ -1,5 +1,7 @@
package_manager_keys:
- src: debian-archive-bullseye-automatic.gpg # Debian 11
dest: /etc/apt/trusted.gpg.d/debian-archive-bullseye-automatic.gpg
+ - src: debian-archive-bullseye-security-automatic.asc # Debian 11
+ dest: /etc/apt/trusted.gpg.d/debian-archive-bullseye-security-automatic.asc
- src: debian-archive-bookworm-automatic.gpg # Debian 12
dest: /etc/apt/trusted.gpg.d/debian-archive-bookworm-automatic.gpg
diff --git a/tests/ansible/integration/_expected_ssh_port.yml b/tests/ansible/integration/_expected_ssh_port.yml
index 442659a5..f85ec3e2 100644
--- a/tests/ansible/integration/_expected_ssh_port.yml
+++ b/tests/ansible/integration/_expected_ssh_port.yml
@@ -11,8 +11,10 @@
- set_fact:
expected_ssh_port: null
- when: ansible_version.full is version('2.11.1', '>=', strict=True)
+ when:
+ - ansible_version_major_minor_patch is version('2.11.1', '>=', strict=True)
- set_fact:
expected_ssh_port: 22
- when: ansible_version.full is version('2.11.1', '<', strict=True)
+ when:
+ - ansible_version_major_minor_patch is version('2.11.1', '<', strict=True)
diff --git a/tests/ansible/integration/action/transfer_data.yml b/tests/ansible/integration/action/transfer_data.yml
index ab994683..c36dd463 100644
--- a/tests/ansible/integration/action/transfer_data.yml
+++ b/tests/ansible/integration/action/transfer_data.yml
@@ -1,7 +1,14 @@
-- name: integration/action/transfer_data.yml
+- name: integration/action/transfer_data.yml, json
hosts: test-targets
tasks:
+ - meta: end_play
+ when:
+ # Ansible >= 12 (ansible-core >= 2.19) only allows bytes|str through
+ # `ansible.plugins.action.ActionBase._transfer_data()`.
+ - ansible_version_major_minor is version('2.19', '>=', strict=True)
+ - not is_mitogen
+
- name: Cleanup transfer data
file:
path: /tmp/transfer-data
@@ -15,26 +22,41 @@
data: {
"I am JSON": true
}
+
- name: Slurp JSON transfer data
slurp:
src: /tmp/transfer-data
register: out
+
- assert:
that: |
out.content|b64decode == '{"I am JSON": true}'
fail_msg: |
out={{ out }}
+ - name: Cleanup transfer data
+ file:
+ path: /tmp/transfer-data
+ state: absent
+ tags:
+ - transfer_data
+
+
+- name: integration/action/transfer_data.yml, text
+ hosts: test-targets
+ tasks:
- name: Create text transfer data
action_passthrough:
method: _transfer_data
kwargs:
remote_path: /tmp/transfer-data
data: "I am text."
+
- name: Slurp text transfer data
slurp:
src: /tmp/transfer-data
register: out
+
- assert:
that:
out.content|b64decode == 'I am text.'
diff --git a/tests/ansible/integration/async/result_shell_echo_hi.yml b/tests/ansible/integration/async/result_shell_echo_hi.yml
index d3f9d42c..6e726fc5 100644
--- a/tests/ansible/integration/async/result_shell_echo_hi.yml
+++ b/tests/ansible/integration/async/result_shell_echo_hi.yml
@@ -35,8 +35,8 @@
# | Ansible <= 3 | ansible-core <= 2.10 | present | True |
# | Ansible 4 - 6 | ansible-core 2.11 - 2.13 | deprecated | False |
# | Ansible >= 7 | ansible-core >= 2.14 | absent | n/a |
- - (ansible_version.full is version("2.14", ">=", strict=True) and async_out.invocation.module_args.warn is not defined)
- or (ansible_version.full is version("2.11", ">=", strict=True) and async_out.invocation.module_args.warn == False)
+ - (ansible_version_major_minor is version("2.14", ">=", strict=True) and async_out.invocation.module_args.warn is not defined)
+ or (ansible_version_major_minor is version("2.11", ">=", strict=True) and async_out.invocation.module_args.warn == False)
or (async_out.invocation.module_args.warn == True)
- async_out.rc == 0
- async_out.start.startswith("20")
@@ -53,7 +53,7 @@
fail_msg: |
async_out={{ async_out }}
when:
- - ansible_version.full is version('2.4', '>=', strict=True)
+ - ansible_version_major_minor is version('2.4', '>=', strict=True)
vars:
async_out: "{{result.content|b64decode|from_json}}"
tags:
diff --git a/tests/ansible/integration/async/runner_one_job.yml b/tests/ansible/integration/async/runner_one_job.yml
index de7a22e6..db9b126f 100644
--- a/tests/ansible/integration/async/runner_one_job.yml
+++ b/tests/ansible/integration/async/runner_one_job.yml
@@ -41,9 +41,9 @@
- result1.changed == True
# ansible/b72e989e1837ccad8dcdc926c43ccbc4d8cdfe44
- |
- (ansible_version.full is version('2.8', ">=", strict=True) and
+ (ansible_version_major_minor is version('2.8', ">=", strict=True) and
result1.cmd == "echo alldone;\nsleep 1;\n") or
- (ansible_version.full is version('2.8', '<', strict=True) and
+ (ansible_version_major_minor is version('2.8', '<', strict=True) and
result1.cmd == "echo alldone;\n sleep 1;")
- result1.delta|length == 14
- result1.start|length == 26
@@ -61,7 +61,7 @@
fail_msg: |
result1={{ result1 }}
when:
- - ansible_version.full is version('2.8', '>', strict=True) # ansible#51393
+ - ansible_version_major_minor is version('2.8', '>', strict=True) # ansible#51393
- assert:
that:
@@ -69,6 +69,6 @@
fail_msg: |
result1={{ result1 }}
when:
- - ansible_version.full is version('2.4', '>', strict=True)
+ - ansible_version_major_minor is version('2.4', '>', strict=True)
tags:
- runner_one_job
diff --git a/tests/ansible/integration/async/runner_two_simultaneous_jobs.yml b/tests/ansible/integration/async/runner_two_simultaneous_jobs.yml
index 74a50318..8c462ec2 100644
--- a/tests/ansible/integration/async/runner_two_simultaneous_jobs.yml
+++ b/tests/ansible/integration/async/runner_two_simultaneous_jobs.yml
@@ -66,6 +66,6 @@
fail_msg: |
result2={{ result2 }}
when:
- - ansible_version.full is version('2.8', '>=', strict=True) # ansible#51393
+ - ansible_version_major_minor is version('2.8', '>=', strict=True) # ansible#51393
tags:
- runner_two_simultaneous_jobs
diff --git a/tests/ansible/integration/connection/reset.yml b/tests/ansible/integration/connection/reset.yml
index 2d7a75d3..9dea33ee 100644
--- a/tests/ansible/integration/connection/reset.yml
+++ b/tests/ansible/integration/connection/reset.yml
@@ -10,11 +10,11 @@
- debug: msg="reset.yml skipped on Ansible<2.5.6"
when:
- - ansible_version.full is version('2.5.6', '<', strict=True)
+ - ansible_version_major_minor_patch is version('2.5.6', '<', strict=True)
- meta: end_play
when:
- - ansible_version.full is version('2.5.6', '<', strict=True)
+ - ansible_version_major_minor_patch is version('2.5.6', '<', strict=True)
- custom_python_detect_environment:
register: out
diff --git a/tests/ansible/integration/connection/reset_become.yml b/tests/ansible/integration/connection/reset_become.yml
index 2548df17..0e0aefa9 100644
--- a/tests/ansible/integration/connection/reset_become.yml
+++ b/tests/ansible/integration/connection/reset_become.yml
@@ -8,11 +8,11 @@
tasks:
- debug: msg="reset_become.yml skipped on Ansible<2.5.6"
when:
- - ansible_version.full is version('2.5.6', '<', strict=True)
+ - ansible_version_major_minor_patch is version('2.5.6', '<', strict=True)
- meta: end_play
when:
- - ansible_version.full is version('2.5.6', '<', strict=True)
+ - ansible_version_major_minor_patch is version('2.5.6', '<', strict=True)
- name: save pid of the become acct
custom_python_detect_environment:
diff --git a/tests/ansible/integration/connection_delegation/delegate_to_template.yml b/tests/ansible/integration/connection_delegation/delegate_to_template.yml
index 60a67b82..8cd50f98 100644
--- a/tests/ansible/integration/connection_delegation/delegate_to_template.yml
+++ b/tests/ansible/integration/connection_delegation/delegate_to_template.yml
@@ -19,7 +19,7 @@
- meta: end_play
when:
- - ansible_version.full is version('2.4', '<', strict=True)
+ - ansible_version_major_minor is version('2.4', '<', strict=True)
- mitogen_get_stack:
delegate_to: "{{ physical_host }}"
@@ -50,7 +50,7 @@
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
- 'ssh_debug_level': null,
+ 'ssh_debug_level': 0,
'ssh_path': 'ssh',
'username': 'alias-user',
},
@@ -78,7 +78,7 @@
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
- 'ssh_debug_level': null,
+ 'ssh_debug_level': 0,
'ssh_path': 'ssh',
'username': 'ansible-cfg-remote-user',
},
diff --git a/tests/ansible/integration/connection_delegation/stack_construction.yml b/tests/ansible/integration/connection_delegation/stack_construction.yml
index 1cfd34ef..58abac7b 100644
--- a/tests/ansible/integration/connection_delegation/stack_construction.yml
+++ b/tests/ansible/integration/connection_delegation/stack_construction.yml
@@ -87,7 +87,7 @@
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
- 'ssh_debug_level': null,
+ 'ssh_debug_level': 0,
'ssh_path': 'ssh',
'username': 'alias-user',
},
@@ -131,7 +131,7 @@
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
- 'ssh_debug_level': null,
+ 'ssh_debug_level': 0,
'ssh_path': 'ssh',
'username': 'alias-user',
},
@@ -186,7 +186,7 @@
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
- 'ssh_debug_level': null,
+ 'ssh_debug_level': 0,
'ssh_path': 'ssh',
'username': 'ansible-cfg-remote-user',
},
@@ -230,7 +230,7 @@
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
- 'ssh_debug_level': null,
+ 'ssh_debug_level': 0,
'ssh_path': 'ssh',
'username': 'alias-user',
},
@@ -258,7 +258,7 @@
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
- 'ssh_debug_level': null,
+ 'ssh_debug_level': 0,
'ssh_path': 'ssh',
'username': 'ansible-cfg-remote-user',
},
@@ -312,7 +312,7 @@
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
- 'ssh_debug_level': null,
+ 'ssh_debug_level': 0,
'ssh_path': 'ssh',
'username': 'newuser-normal-normal-user',
},
@@ -357,7 +357,7 @@
-o, PubkeyAcceptedKeyTypes=+ssh-rsa,
-o, UserKnownHostsFile=/dev/null,
],
- 'ssh_debug_level': null,
+ 'ssh_debug_level': 0,
'ssh_path': 'ssh',
'username': 'alias-user',
},
diff --git a/tests/ansible/integration/context_service/disconnect_cleanup.yml b/tests/ansible/integration/context_service/disconnect_cleanup.yml
index 22ba12eb..a87dae3d 100644
--- a/tests/ansible/integration/context_service/disconnect_cleanup.yml
+++ b/tests/ansible/integration/context_service/disconnect_cleanup.yml
@@ -8,7 +8,7 @@
- meta: end_play
when:
- - ansible_version.full is version('2.5.6', '<', strict=True)
+ - ansible_version_major_minor_patch is version('2.5.6', '<', strict=True)
# Start with a clean slate.
- mitogen_shutdown_all:
diff --git a/tests/ansible/integration/interpreter_discovery/ansible_2_8_tests.yml b/tests/ansible/integration/interpreter_discovery/ansible_2_8_tests.yml
index eddca199..451b4dc3 100644
--- a/tests/ansible/integration/interpreter_discovery/ansible_2_8_tests.yml
+++ b/tests/ansible/integration/interpreter_discovery/ansible_2_8_tests.yml
@@ -46,9 +46,9 @@
'20': /usr/bin/python3.8
discovered_interpreter_expected: >-
- {%- if ansible_version.full is version('2.12', '<', strict=True) -%}
+ {%- if ansible_version_major_minor is version('2.12', '<', strict=True) -%}
{{ DISCOVERED_INTERPRETER_EXPECTED_MAP__ANSIBLE_lt_2_12[distro][distro_major] }}
- {%- elif ansible_version.full is version('2.17', '<', strict=True) -%}
+ {%- elif ansible_version_major_minor is version('2.17', '<', strict=True) -%}
{{ DISCOVERED_INTERPRETER_EXPECTED_MAP__ANSIBLE_2_12_to_2_16[distro][distro_major] }}
{%- else -%}
{{ DISCOVERED_INTERPRETER_EXPECTED_MAP__ANSIBLE_ge_2_17[distro][distro_major] }}
@@ -141,7 +141,7 @@
when:
- legacy.ansible_facts.discovered_interpreter_python == '/usr/bin/python'
- auto_out.ansible_facts.discovered_interpreter_python != '/usr/bin/python'
- - ansible_version.full is version_compare('2.12.0', '<', strict=True)
+ - ansible_version_major_minor is version('2.12', '<', strict=True)
- name: check for warning (only on platforms where auto result is not /usr/bin/python and legacy is) from ansible 2.12 on
assert:
@@ -153,7 +153,7 @@
when:
- legacy.ansible_facts.discovered_interpreter_python == '/usr/bin/python'
- auto_out.ansible_facts.discovered_interpreter_python != '/usr/bin/python'
- - ansible_version.full is version_compare('2.12.0', '>=', strict=True)
+ - ansible_version_major_minor is version('2.12', '>=', strict=True)
- name: test that auto_silent never warns and got the same answer as auto
block:
@@ -229,6 +229,6 @@
always:
- meta: clear_facts
when:
- - ansible_version.full is version_compare('2.8.0', '>=', strict=True)
+ - ansible_version_major_minor is version('2.8', '>=', strict=True)
tags:
- ansible_2_8_tests
diff --git a/tests/ansible/integration/interpreter_discovery/complex_args.yml b/tests/ansible/integration/interpreter_discovery/complex_args.yml
index f9770876..555a676b 100644
--- a/tests/ansible/integration/interpreter_discovery/complex_args.yml
+++ b/tests/ansible/integration/interpreter_discovery/complex_args.yml
@@ -14,7 +14,7 @@
- meta: end_play
when:
- not is_mitogen
- - ansible_version.full is version('2.17.1', '>=', strict=True)
+ - ansible_version_major_minor_patch is version('2.17.1', '>=', strict=True)
- name: create temp file to source
file:
diff --git a/tests/ansible/integration/process/unix_socket_cleanup.yml b/tests/ansible/integration/process/unix_socket_cleanup.yml
index 21747494..4466aa2e 100644
--- a/tests/ansible/integration/process/unix_socket_cleanup.yml
+++ b/tests/ansible/integration/process/unix_socket_cleanup.yml
@@ -11,6 +11,8 @@
vars:
ansible_python_interpreter: "{{ ansible_playbook_python }}"
shell: >-
+ ANSIBLE_CALLBACK_RESULT_FORMAT=json
+ ANSIBLE_LOAD_CALLBACK_PLUGINS=false
ANSIBLE_STRATEGY=mitogen_linear
ANSIBLE_SSH_ARGS="-o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa"
ANSIBLE_VERBOSITY="{{ ansible_verbosity }}"
diff --git a/tests/ansible/integration/runner/crashy_new_style_module.yml b/tests/ansible/integration/runner/crashy_new_style_module.yml
index 80833ab8..5a4d6651 100644
--- a/tests/ansible/integration/runner/crashy_new_style_module.yml
+++ b/tests/ansible/integration/runner/crashy_new_style_module.yml
@@ -18,10 +18,17 @@
- not out.changed
- out is failed
# https://github.com/ansible/ansible/commit/62d8c8fde6a76d9c567ded381e9b34dad69afcd6
- - out.msg is match(msg_pattern)
- - (out.module_stdout == "" and out.module_stderr is search(tb_pattern))
- or
- (out.module_stdout is search(tb_pattern) and out.module_stderr is match("Shared connection to localhost closed."))
+ - |
+ out.msg is match(msg_pattern)
+ or out.msg in (
+ "Task failed: Module failed: name 'kaboom' is not defined",
+ 'Module result deserialization failed: No start of json char found',
+ )
+ # - out.exception is undefined
+ # or out.exception | default('') is match(tb_pattern)
+ # or out.module_stderr is search(tb_pattern)
+ # - out.module_stdout == ''
+ # - out.module_stderr is search(tb_pattern)
fail_msg: |
out={{ out }}
tags:
diff --git a/tests/ansible/integration/runner/custom_bash_hashbang_argument.yml b/tests/ansible/integration/runner/custom_bash_hashbang_argument.yml
index 34a60e61..c750317a 100644
--- a/tests/ansible/integration/runner/custom_bash_hashbang_argument.yml
+++ b/tests/ansible/integration/runner/custom_bash_hashbang_argument.yml
@@ -7,7 +7,7 @@
- meta: end_play
when:
- not is_mitogen
- - ansible_version.full is version('2.17.1', '>=', strict=True)
+ - ansible_version_major_minor_patch is version('2.17.1', '>=', strict=True)
- custom_bash_old_style_module:
foo: true
diff --git a/tests/ansible/integration/runner/custom_binary_producing_junk.yml b/tests/ansible/integration/runner/custom_binary_producing_junk.yml
index 2a05fb75..c8ab869a 100644
--- a/tests/ansible/integration/runner/custom_binary_producing_junk.yml
+++ b/tests/ansible/integration/runner/custom_binary_producing_junk.yml
@@ -30,6 +30,7 @@
- out.failed
- out.results[0].failed
- out.results[0].msg.startswith('MODULE FAILURE')
+ or out.results[0].msg.startswith('Module result deserialization failed')
- out.results[0].rc == 0
fail_msg: |
out={{ out }}
diff --git a/tests/ansible/integration/runner/custom_binary_single_null.yml b/tests/ansible/integration/runner/custom_binary_single_null.yml
index cfd401f8..bb5ec5d0 100644
--- a/tests/ansible/integration/runner/custom_binary_single_null.yml
+++ b/tests/ansible/integration/runner/custom_binary_single_null.yml
@@ -15,7 +15,9 @@
that:
- "out.failed"
- "out.results[0].failed"
- - "out.results[0].msg.startswith('MODULE FAILURE')"
+ - |
+ out.results[0].msg.startswith('MODULE FAILURE')
+ or out.results[0].msg == 'Module result deserialization failed: No start of json char found'
# On Ubuntu 16.04 /bin/sh is dash 0.5.8. It treats custom_binary_single_null
# as a valid executable. There's no error message, and rc == 0.
- |
diff --git a/tests/ansible/integration/runner/custom_perl_json_args_module.yml b/tests/ansible/integration/runner/custom_perl_json_args_module.yml
index a34b6b75..c5c647cd 100644
--- a/tests/ansible/integration/runner/custom_perl_json_args_module.yml
+++ b/tests/ansible/integration/runner/custom_perl_json_args_module.yml
@@ -20,6 +20,6 @@
fail_msg: |
out={{ out }}
when:
- - ansible_version.full is version('2.4', '>=', strict=True)
+ - ansible_version_major_minor is version('2.4', '>=', strict=True)
tags:
- custom_perl_json_args_module
diff --git a/tests/ansible/integration/runner/custom_perl_want_json_module.yml b/tests/ansible/integration/runner/custom_perl_want_json_module.yml
index 28ad7f7f..91ec5672 100644
--- a/tests/ansible/integration/runner/custom_perl_want_json_module.yml
+++ b/tests/ansible/integration/runner/custom_perl_want_json_module.yml
@@ -20,6 +20,6 @@
fail_msg: |
out={{ out }}
when:
- - ansible_version.full is version('2.4', '>=', strict=True)
+ - ansible_version_major_minor is version('2.4', '>=', strict=True)
tags:
- custom_perl_want_json_module
diff --git a/tests/ansible/integration/runner/missing_module.yml b/tests/ansible/integration/runner/missing_module.yml
index 4d3f6823..b641cbe3 100644
--- a/tests/ansible/integration/runner/missing_module.yml
+++ b/tests/ansible/integration/runner/missing_module.yml
@@ -5,6 +5,8 @@
- name: Run missing_module
connection: local
environment:
+ ANSIBLE_CALLBACK_RESULT_FORMAT: json
+ ANSIBLE_LOAD_CALLBACK_PLUGINS: "false"
ANSIBLE_STRATEGY: "{{ lookup('env', 'ANSIBLE_STRATEGY') | mandatory }}"
ANSIBLE_VERBOSITY: "{{ ansible_verbosity }}"
vars:
@@ -26,6 +28,7 @@
- assert:
that: |
'The module missing_module was not found in configured module paths' in out.stdout
+ or "Cannot resolve 'missing_module' to an action or module" in out.stdout
fail_msg: |
out={{ out }}
tags:
diff --git a/tests/ansible/integration/ssh/all.yml b/tests/ansible/integration/ssh/all.yml
index 20031704..6dbf945f 100644
--- a/tests/ansible/integration/ssh/all.yml
+++ b/tests/ansible/integration/ssh/all.yml
@@ -8,3 +8,4 @@
- import_playbook: templated_by_play_taskvar.yml
- import_playbook: templated_by_task_keyword.yml
- import_playbook: variables.yml
+- import_playbook: verbosity.yml
diff --git a/tests/ansible/integration/ssh/password.yml b/tests/ansible/integration/ssh/password.yml
index 21ab6f15..ca08fa5b 100644
--- a/tests/ansible/integration/ssh/password.yml
+++ b/tests/ansible/integration/ssh/password.yml
@@ -31,6 +31,11 @@
- assert:
that:
- ssh_no_password_result.unreachable == True
+ - >-
+ ssh_no_password_result.msg is search('SSH password was requested, but none specified')
+ or ssh_no_password_result.msg is search('SSH password is incorrect')
+ or ssh_no_password_result.msg is search('Invalid/incorrect password')
+ or ssh_no_password_result.msg is search('Permission denied \(publickey,password(,keyboard-interactive)?\)')
fail_msg: |
ssh_no_password_result={{ ssh_no_password_result }}
@@ -64,5 +69,9 @@
- assert:
that:
- ssh_wrong_password_result.unreachable == True
+ - >-
+ ssh_wrong_password_result.msg is search('SSH password is incorrect')
+ or ssh_wrong_password_result.msg is search('Invalid/incorrect password')
+ or ssh_wrong_password_result.msg is search('Permission denied \(publickey,password(,keyboard-interactive)?\)')
fail_msg: |
ssh_wrong_password_result={{ ssh_wrong_password_result }}
diff --git a/tests/ansible/integration/ssh/templated_by_play_taskvar.yml b/tests/ansible/integration/ssh/templated_by_play_taskvar.yml
index c5c2e544..714d05dc 100644
--- a/tests/ansible/integration/ssh/templated_by_play_taskvar.yml
+++ b/tests/ansible/integration/ssh/templated_by_play_taskvar.yml
@@ -32,7 +32,7 @@
when:
# https://github.com/ansible/ansible/issues/84238
- not is_mitogen
- - ansible_version.full is version('2.19', '<', strict=True)
+ - ansible_version_major_minor is version('2.19', '<', strict=True)
- meta: reset_connection
- name: Templated variables in play, key authentication
ping:
diff --git a/tests/ansible/integration/ssh/timeouts.yml b/tests/ansible/integration/ssh/timeouts.yml
index afc5e5a2..0cb59422 100644
--- a/tests/ansible/integration/ssh/timeouts.yml
+++ b/tests/ansible/integration/ssh/timeouts.yml
@@ -13,6 +13,8 @@
- name: Cause Ansible connection timeout
connection: local
environment:
+ ANSIBLE_CALLBACK_RESULT_FORMAT: json
+ ANSIBLE_LOAD_CALLBACK_PLUGINS: "false"
ANSIBLE_SSH_TIMEOUT: 10
ANSIBLE_STRATEGY: "{{ lookup('env', 'ANSIBLE_STRATEGY') | mandatory }}"
ANSIBLE_VERBOSITY: "{{ ansible_verbosity }}"
@@ -43,6 +45,7 @@
'"unreachable": true' in out.stdout
- |
'"msg": "Connection timed out."' in out.stdout
+ or '"msg": "Task failed: Connection timed out."' in out.stdout
fail_msg: |
out={{ out }}
tags:
diff --git a/tests/ansible/integration/ssh/variables.yml b/tests/ansible/integration/ssh/variables.yml
index bb4bd179..5eb54dde 100644
--- a/tests/ansible/integration/ssh/variables.yml
+++ b/tests/ansible/integration/ssh/variables.yml
@@ -19,6 +19,8 @@
- name: ansible_user, ansible_ssh_private_key_file
shell: >
ANSIBLE_ANY_ERRORS_FATAL=false
+ ANSIBLE_CALLBACK_RESULT_FORMAT=json
+ ANSIBLE_LOAD_CALLBACK_PLUGINS=false
ANSIBLE_STRATEGY=mitogen_linear
ANSIBLE_SSH_ARGS="-o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa"
ANSIBLE_VERBOSITY="{{ ansible_verbosity }}"
@@ -37,6 +39,8 @@
- name: ansible_user, wrong ansible_ssh_private_key_file
shell: >
ANSIBLE_ANY_ERRORS_FATAL=false
+ ANSIBLE_CALLBACK_RESULT_FORMAT=json
+ ANSIBLE_LOAD_CALLBACK_PLUGINS=false
ANSIBLE_STRATEGY=mitogen_linear
ANSIBLE_SSH_ARGS="-o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa"
ANSIBLE_VERBOSITY="{{ ansible_verbosity }}"
diff --git a/tests/ansible/integration/ssh/verbosity.yml b/tests/ansible/integration/ssh/verbosity.yml
new file mode 100644
index 00000000..2bf1ed10
--- /dev/null
+++ b/tests/ansible/integration/ssh/verbosity.yml
@@ -0,0 +1,41 @@
+# Ansible >= 12 (ansible-core >= 2.19) adds SSH connection `verbosity` property.
+# Ansible <= 11 (ansible-core <= 2.18) applies ANSIBLE_VERBOSITY -> ssh.
+
+- name: integration/ssh/verbosity.yml
+ hosts: test-targets
+ gather_facts: false
+ tasks:
+ - meta: end_play
+ when:
+ - ansible_version_major_minor is version('2.19', '<', strict=True)
+
+ - name: Exercise ssh verbosity
+ connection: local
+ environment:
+ ANSIBLE_CALLBACK_RESULT_FORMAT: json
+ ANSIBLE_LOAD_CALLBACK_PLUGINS: "false"
+ ANSIBLE_SSH_VERBOSITY: 3
+ ANSIBLE_STRATEGY: "{{ lookup('env', 'ANSIBLE_STRATEGY') | mandatory }}"
+ ANSIBLE_VERBOSITY: 3
+ vars:
+ ansible_python_interpreter: "{{ ansible_playbook_python }}"
+ command:
+ cmd:
+ ansible
+ {% for inv in ansible_inventory_sources %}
+ -i "{{ inv }}"
+ {% endfor %}
+ "{{ inventory_hostname }}"
+ -m ping
+ chdir: ../..
+ register: out
+ changed_when: false
+ check_mode: false
+
+ - name: Verify ssh -vvv output is included
+ assert:
+ that:
+ - >-
+ out.stdout is search('debug1: Reading configuration data')
+ fail_msg: |
+ out={{ out }}
diff --git a/tests/ansible/integration/stub_connections/_end_play_if_not_sudo_linux.yml b/tests/ansible/integration/stub_connections/_end_play_if_not_sudo_linux.yml
index a53f75ed..3a4f4e6e 100644
--- a/tests/ansible/integration/stub_connections/_end_play_if_not_sudo_linux.yml
+++ b/tests/ansible/integration/stub_connections/_end_play_if_not_sudo_linux.yml
@@ -9,7 +9,7 @@
- command: sudo -n whoami
args:
- warn: "{{ False if ansible_version.full is version('2.10', '<=', strict=True) else omit }}"
+ warn: "{{ False if ansible_version_major_minor is version('2.10', '<=', strict=True) else omit }}"
ignore_errors: true
register: sudo_available
diff --git a/tests/ansible/integration/stub_connections/kubectl.yml b/tests/ansible/integration/stub_connections/kubectl.yml
index 8fe061d1..c9486d90 100644
--- a/tests/ansible/integration/stub_connections/kubectl.yml
+++ b/tests/ansible/integration/stub_connections/kubectl.yml
@@ -7,7 +7,7 @@
- meta: end_play
when:
- - ansible_version.full is version('2.5', '<', strict=True)
+ - ansible_version_major_minor is version('2.5', '<', strict=True)
- custom_python_detect_environment:
vars:
diff --git a/tests/ansible/integration/stub_connections/setns_lxc.yml b/tests/ansible/integration/stub_connections/setns_lxc.yml
index 584a6806..dc7340a8 100644
--- a/tests/ansible/integration/stub_connections/setns_lxc.yml
+++ b/tests/ansible/integration/stub_connections/setns_lxc.yml
@@ -15,6 +15,8 @@
- name: Run stub-lxc-info.py
environment:
+ ANSIBLE_CALLBACK_RESULT_FORMAT: json
+ ANSIBLE_LOAD_CALLBACK_PLUGINS: "false"
ANSIBLE_STRATEGY: "{{ lookup('env', 'ANSIBLE_STRATEGY') | mandatory }}"
ANSIBLE_VERBOSITY: "{{ ansible_verbosity }}"
command: |
@@ -30,7 +32,7 @@
localhost
args:
chdir: ../..
- warn: "{{ False if ansible_version.full is version('2.10', '<=', strict=True) else omit }}"
+ warn: "{{ False if ansible_version_major_minor is version('2.10', '<=', strict=True) else omit }}"
register: result
- assert:
diff --git a/tests/ansible/integration/stub_connections/setns_lxd.yml b/tests/ansible/integration/stub_connections/setns_lxd.yml
index 2e07aca3..127941f0 100644
--- a/tests/ansible/integration/stub_connections/setns_lxd.yml
+++ b/tests/ansible/integration/stub_connections/setns_lxd.yml
@@ -15,6 +15,8 @@
- name: Run ansible stub-lxc.py
environment:
+ ANSIBLE_CALLBACK_RESULT_FORMAT: json
+ ANSIBLE_LOAD_CALLBACK_PLUGINS: "false"
ANSIBLE_STRATEGY: "{{ lookup('env', 'ANSIBLE_STRATEGY') | mandatory }}"
ANSIBLE_VERBOSITY: "{{ ansible_verbosity }}"
command: |
@@ -30,7 +32,7 @@
localhost
args:
chdir: ../..
- warn: "{{ False if ansible_version.full is version('2.10', '<=', strict=True) else omit }}"
+ warn: "{{ False if ansible_version_major_minor is version('2.10', '<=', strict=True) else omit }}"
register: result
- assert:
diff --git a/tests/ansible/integration/transport_config/password.yml b/tests/ansible/integration/transport_config/password.yml
index 5a1968e0..34ed1a41 100644
--- a/tests/ansible/integration/transport_config/password.yml
+++ b/tests/ansible/integration/transport_config/password.yml
@@ -8,9 +8,16 @@
tasks:
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
+ - assert_equal:
+ left: out.result[0].kwargs.password
+ right: null
+ when:
+ - ansible_version_major_minor is version('2.19', '>=', strict=True)
- assert_equal:
left: out.result[0].kwargs.password
right: "" # actually null, but assert_equal limitation
+ when:
+ - ansible_version_major_minor is version('2.19', '<', strict=True)
tags:
- mitogen_only
@@ -23,9 +30,16 @@
- assert_equal:
left: out.result[0].kwargs.password
right: "ansi-ssh-pass"
+ - assert_equal:
+ left: out.result[1].kwargs.password
+ right: null
+ when:
+ - ansible_version_major_minor is version('2.19', '>=', strict=True)
- assert_equal:
left: out.result[1].kwargs.password
right: ""
+ when:
+ - ansible_version_major_minor is version('2.19', '<', strict=True)
tags:
- mitogen_only
@@ -48,9 +62,16 @@
tasks:
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
+ - assert_equal:
+ left: out.result[0].kwargs.password
+ right: null
+ when:
+ - ansible_version_major_minor is version('2.19', '>=', strict=True)
- assert_equal:
left: out.result[0].kwargs.password
right: ""
+ when:
+ - ansible_version_major_minor is version('2.19', '<', strict=True)
- assert_equal:
left: out.result[1].kwargs.password
right: "ansi-ssh-pass"
@@ -76,9 +97,16 @@
tasks:
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
+ - assert_equal:
+ left: out.result[0].kwargs.password
+ right: null
+ when:
+ - ansible_version_major_minor is version('2.19', '>=', strict=True)
- assert_equal:
left: out.result[0].kwargs.password
right: ""
+ when:
+ - ansible_version_major_minor is version('2.19', '<', strict=True)
- assert_equal:
left: out.result[1].kwargs.password
right: "ansi-pass"
@@ -104,9 +132,16 @@
tasks:
- include_tasks: ../_mitogen_only.yml
- {mitogen_get_stack: {}, register: out}
+ - assert_equal:
+ left: out.result[0].kwargs.password
+ right: null
+ when:
+ - ansible_version_major_minor is version('2.19', '>=', strict=True)
- assert_equal:
left: out.result[0].kwargs.password
right: ""
+ when:
+ - ansible_version_major_minor is version('2.19', '<', strict=True)
- assert_equal:
left: out.result[1].kwargs.password
right: "c.b.a"
diff --git a/tests/ansible/integration/transport_config/python_path.yml b/tests/ansible/integration/transport_config/python_path.yml
index 21f3928c..b8bb286b 100644
--- a/tests/ansible/integration/transport_config/python_path.yml
+++ b/tests/ansible/integration/transport_config/python_path.yml
@@ -12,7 +12,7 @@
- include_tasks: ../_mitogen_only.yml
- meta: end_play
when:
- - ansible_version.full is version('2.17', '>=', strict=True)
+ - ansible_version_major_minor is version('2.17', '>=', strict=True)
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
@@ -27,7 +27,7 @@
- include_tasks: ../_mitogen_only.yml
- meta: end_play
when:
- - ansible_version.full is version('2.17', '>=', strict=True)
+ - ansible_version_major_minor is version('2.17', '>=', strict=True)
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
@@ -57,7 +57,7 @@
- include_tasks: ../_mitogen_only.yml
- meta: end_play
when:
- - ansible_version.full is version('2.17', '>=', strict=True)
+ - ansible_version_major_minor is version('2.17', '>=', strict=True)
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
@@ -87,7 +87,7 @@
- include_tasks: ../_mitogen_only.yml
- meta: end_play
when:
- - ansible_version.full is version('2.17', '>=', strict=True)
+ - ansible_version_major_minor is version('2.17', '>=', strict=True)
- {mitogen_get_stack: {}, register: out}
- assert_equal:
left: out.result[0].kwargs.python_path
diff --git a/tests/ansible/regression/issue_1066__add_host__host_key_checking.yml b/tests/ansible/regression/issue_1066__add_host__host_key_checking.yml
index dd754f84..cdc50fc3 100644
--- a/tests/ansible/regression/issue_1066__add_host__host_key_checking.yml
+++ b/tests/ansible/regression/issue_1066__add_host__host_key_checking.yml
@@ -31,7 +31,7 @@
# FIXME https://github.com/mitogen-hq/mitogen/issues/1096
- meta: end_play
when:
- - ansible_version.full is version('2.17', '>=', strict=True)
+ - ansible_version_major_minor is version('2.17', '>=', strict=True)
- meta: reset_connection
# The host key might be in ~/.ssh/known_hosts. If it's removed then no
@@ -65,7 +65,7 @@
# ansible_host_key_checking don't work on Vanilla Ansible 2.10, even for
# static inventory hosts (ansible/ansible#49254, ansible/ansible#73708).
when:
- - ansible_version.full is version('2.11', '>=', strict=True)
+ - ansible_version_major_minor is version('2.11', '>=', strict=True)
or is_mitogen
tags:
- issue_1066
diff --git a/tests/ansible/regression/issue_109__target_has_old_ansible_installed.yml b/tests/ansible/regression/issue_109__target_has_old_ansible_installed.yml
index a7ae0908..550a7f4c 100644
--- a/tests/ansible/regression/issue_109__target_has_old_ansible_installed.yml
+++ b/tests/ansible/regression/issue_109__target_has_old_ansible_installed.yml
@@ -7,7 +7,7 @@
tasks:
- meta: end_play
when:
- - ansible_version.full is version('2.6', '<', strict=True)
+ - ansible_version_major_minor is version('2.6', '<', strict=True)
- name: Copy the naughty ansible into place
copy:
@@ -24,7 +24,8 @@
assert:
that:
- env.cwd == ansible_user_dir
- - (not env.mitogen_loaded) or (env.python_path.count("") == 1)
+ - not env.mitogen_loaded
+ or (env.python_path | select('eq', '') | length == 1)
fail_msg: |
ansible_user_dir={{ ansible_user_dir }}
env={{ env }}
diff --git a/tests/ansible/regression/issue_590__sys_modules_crap.yml b/tests/ansible/regression/issue_590__sys_modules_crap.yml
index 2c2262f4..f2bda94c 100644
--- a/tests/ansible/regression/issue_590__sys_modules_crap.yml
+++ b/tests/ansible/regression/issue_590__sys_modules_crap.yml
@@ -3,7 +3,7 @@
tasks:
- meta: end_play
when:
- - ansible_version.full is version('2.8', '<', strict=True)
+ - ansible_version_major_minor is version('2.8', '<', strict=True)
- custom_python_uses_distro:
register: out
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 506d5516..297b6043 100644
--- a/tests/ansible/regression/issue_655__wait_for_connection_error.yml
+++ b/tests/ansible/regression/issue_655__wait_for_connection_error.yml
@@ -22,7 +22,7 @@
when:
# Ansible 10 (ansible-core 2.17+) require Python 3.7+ on targets.
# On CentOS 8 /usr/libexec/platform-python is Python 3.6
- - ansible_version.full is version('2.17', '>=', strict=True)
+ - ansible_version_major_minor is version('2.17', '>=', strict=True)
- name: set up test container and run tests inside it
block:
diff --git a/tests/ansible/regression/issue_766__get_with_context.yml b/tests/ansible/regression/issue_766__get_with_context.yml
index 38e33275..5dae142f 100644
--- a/tests/ansible/regression/issue_766__get_with_context.yml
+++ b/tests/ansible/regression/issue_766__get_with_context.yml
@@ -25,8 +25,15 @@
# 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)
+ - ansible_version_major_minor is version('2.11', '>=', strict=True)
+ - ansible_version_major_minor is version('2.12', '<', strict=True)
+
+ - meta: end_play
+ when:
+ # TASK [Get running configuration and state data ]
+ # Error: : Task failed: ActionBase._parse_returned_data() missing 1 required positional argument: 'profile'
+ # https://github.com/ansible-collections/ansible.netcommon/issues/698#issuecomment-2910082548
+ - ansible_version_major_minor is version('2.19', '>=', strict=True)
- block:
- name: Start container
diff --git a/tests/ansible/regression/issue_776__load_plugins_called_twice.yml b/tests/ansible/regression/issue_776__load_plugins_called_twice.yml
index d482c41c..4a4cf7be 100755
--- a/tests/ansible/regression/issue_776__load_plugins_called_twice.yml
+++ b/tests/ansible/regression/issue_776__load_plugins_called_twice.yml
@@ -17,7 +17,7 @@
# support Python 2.x on targets.
- meta: end_play
when:
- - ansible_version.full is version('2.17', '>=', strict=True)
+ - ansible_version_major_minor is version('2.17', '>=', strict=True)
roles:
- role: package_manager
diff --git a/tests/ansible/tests/utils_unsafe_test.py b/tests/ansible/tests/utils_unsafe_test.py
index 9aa461c5..2ca863b7 100644
--- a/tests/ansible/tests/utils_unsafe_test.py
+++ b/tests/ansible/tests/utils_unsafe_test.py
@@ -4,6 +4,7 @@ from ansible.utils.unsafe_proxy import AnsibleUnsafeBytes
from ansible.utils.unsafe_proxy import AnsibleUnsafeText
from ansible.utils.unsafe_proxy import wrap_var
+import ansible_mitogen.utils
import ansible_mitogen.utils.unsafe
import mitogen.core
@@ -17,7 +18,7 @@ class Text(mitogen.core.UnicodeType): pass
class Tuple(tuple): pass
-class CastTest(unittest.TestCase):
+class CastMixin(unittest.TestCase):
def assertIsType(self, obj, cls, msg=None):
self.assertIs(type(obj), cls, msg)
@@ -29,6 +30,8 @@ class CastTest(unittest.TestCase):
self.assertEqual(cast(obj), expected)
self.assertIsType(cast(obj), type(expected))
+
+class CastKnownTest(CastMixin):
def test_ansible_unsafe(self):
self.assertCasts(AnsibleUnsafeBytes(b'abc'), b'abc')
self.assertCasts(AnsibleUnsafeText(u'abc'), u'abc')
@@ -47,14 +50,12 @@ class CastTest(unittest.TestCase):
self.assertCasts(wrap_var({}), {})
self.assertCasts(wrap_var([]), [])
self.assertCasts(wrap_var(u''), u'')
- self.assertCasts(wrap_var(()), [])
def test_subtypes_roundtrip(self):
self.assertCasts(wrap_var(Bytes()), b'')
self.assertCasts(wrap_var(Dict()), {})
self.assertCasts(wrap_var(List()), [])
self.assertCasts(wrap_var(Text()), u'')
- self.assertCasts(wrap_var(Tuple()), [])
def test_subtype_nested_dict(self):
obj = Dict(foo=Dict(bar=u'abc'))
@@ -75,18 +76,59 @@ class CastTest(unittest.TestCase):
self.assertIsType(unwrapped[0], list)
self.assertIsType(unwrapped[0][0], mitogen.core.UnicodeType)
- def test_subtype_roundtrip_tuple(self):
- # wrap_var() preserves sequence types, cast() does not (for now)
+
+@unittest.skipIf(
+ ansible_mitogen.utils.ansible_version[:2] <= (2, 18),
+ 'Ansible <= 11 (ansible-core >= 2.18) does not send/receive sets',
+)
+class CastSetTest(CastMixin):
+ def test_set(self):
+ self.assertCasts(wrap_var(set()), set())
+
+ def test_set_subclass(self):
+ self.assertCasts(wrap_var(Set()), set())
+
+
+class CastTupleTest(CastMixin):
+ def test_tuple(self):
+ if ansible_mitogen.utils.ansible_version[:2] >= (2, 19):
+ expected = ()
+ else:
+ expected = []
+ self.assertCasts(wrap_var(Tuple()), expected)
+
+ def test_tuple_subclass(self):
+ if ansible_mitogen.utils.ansible_version[:2] >= (2, 19):
+ expected = ()
+ else:
+ expected = []
+ self.assertCasts(wrap_var(()), expected)
+
+ def test_tuple_subclass_with_contents(self):
+ if ansible_mitogen.utils.ansible_version[:2] >= (2, 19):
+ expected = ((u'abc',),)
+ else:
+ expected = [[u'abc']]
+
obj = Tuple([Tuple([u'abc'])])
wrapped = wrap_var(obj)
unwrapped = ansible_mitogen.utils.unsafe.cast(wrapped)
- self.assertEqual(unwrapped, [[u'abc']])
- self.assertIsType(unwrapped, list)
- self.assertIsType(unwrapped[0], list)
+ self.assertEqual(unwrapped, expected)
+ self.assertIsType(unwrapped, type(expected))
+ self.assertIsType(unwrapped[0], type(expected[0]))
self.assertIsType(unwrapped[0][0], mitogen.core.UnicodeType)
- def test_unknown_types_raise(self):
+
+class CastUknownTypeTest(unittest.TestCase):
+ @unittest.skipIf(
+ ansible_mitogen.utils.ansible_version[:2] >= (2, 19),
+ 'Ansible >= 12 (ansible-core >= 2.19) uses/preserves sets',
+ )
+ def test_set_raises(self):
cast = ansible_mitogen.utils.unsafe.cast
self.assertRaises(TypeError, cast, set())
self.assertRaises(TypeError, cast, Set())
+
+ def test_complex_raises(self):
+ cast = ansible_mitogen.utils.unsafe.cast
self.assertRaises(TypeError, cast, 4j)
diff --git a/tests/image_prep/_container_setup.yml b/tests/image_prep/_container_setup.yml
index 4aa3b46d..b95d67a9 100644
--- a/tests/image_prep/_container_setup.yml
+++ b/tests/image_prep/_container_setup.yml
@@ -66,7 +66,7 @@
dnf: dnf clean all
command: "{{ clean_command[ansible_pkg_mgr] }}"
args:
- warn: "{{ False if ansible_version.full is version('2.10', '<=', strict=True) else omit }}"
+ warn: "{{ False if ansible_version_major_minor is version('2.10', '<=', strict=True) else omit }}"
- name: Clean up apt package lists
shell: rm -rf {{item}}/*
diff --git a/tests/image_prep/group_vars/all.yml b/tests/image_prep/group_vars/all.yml
index 91ff934d..6545e432 100644
--- a/tests/image_prep/group_vars/all.yml
+++ b/tests/image_prep/group_vars/all.yml
@@ -1,3 +1,5 @@
+ansible_version_major_minor: "{{ ansible_version.major }}.{{ ansible_version.minor }}"
+
common_packages:
- openssh-server
- rsync
diff --git a/tests/image_prep/roles/package_manager/files/debian-archive-bullseye-security-automatic.asc b/tests/image_prep/roles/package_manager/files/debian-archive-bullseye-security-automatic.asc
new file mode 100644
index 00000000..e09eef42
--- /dev/null
+++ b/tests/image_prep/roles/package_manager/files/debian-archive-bullseye-security-automatic.asc
@@ -0,0 +1,186 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBGAEHLABEACob9fgQVEt9lqNWKPyzMdenmg+sIE+1ZXwUn6QzJhGedE42FY6
+ov6NAzYh08DOPYZsxpU7C5vX9nuM2Fp1tKgGXIfQZmc6EpLsYmPsKpAFOHfKs1SL
+bcwgc9pgLvJ6ZvUS/c2T2SHxMStHyFlJbMkLd8B/DQSx8XaIvjlHWiTiLv/+UuAf
+d4yQeatMyPvhnVLuUf5Utgdvl5Twwm47IxUMX9426VKg19/22uJyWN0kfI0uLy7h
+g1cHArR5JOoiPRf1xR4ZF3zgu4gwCDD1Puv8iJuWM2U0DQDPKOuH2DdasezHiGCV
+rQ9LWijTZvpyT/fg1qaY3w/1gx8QK6TpsFL3Fwxopx2VrD7e2+FX3mmxfqhJGlAA
+fG0gOpie6t2WH6dfcubWCt8hjY2gN+NT24gotDqk6Uz3TgLDG439+A6Tazji2shv
+Qp74iTpVjyiBsdjF8ZbLBX1mGFLjniuZxuzOk/skUaInZ6g4SGw2qy8f0uBbdPxe
+IuNe8QLxEotXt5YCh265BDp6QpnHh5qfFc3IqwBA0hjkgvuzH+uNm1lA2dlKscPs
+qntw2c2epN4w/H8VZYlv80KBEHx7vaneoVMxQkYDTNA2pJJJvWO1fKnIlpPMu4HW
+eAeiFOYnju5/Vdz4JuBmOQ9ATiHfZDBuC35IWzU1r/Tq6LoPIqKm13xJawARAQAB
+iQJOBB8BCgA4FiEErFMNUg8vMmn16YMTpIRJBEqtXF0FAmAEHLUXDIABgOl28UpQ
+ikjpyj/pvDciUsoc+WQCBwAACgkQpIRJBEqtXF1npA//RSkQvkVQqOtQdoZliUKF
+R2w1RZrH7BXRMDudrjOcJ44GNuhrwPndnDYXEmEmIKKXamT30BwDiD9sn4Xmwr6r
+8YkO0lE9vvL6vvP385P7mdDmd0uqH9jm8fxQelOwuf/8IAFohthBi6ajfsPUTgGn
+cGXqAUvExlShhXZK/rq+3lWFy+hhyxKC0nrEMGskiATUY2HyQoiy47BheAWQs5Is
+Qfc43QS/C0ySgrNsm8KENlUcAAntRdutL1JV8ORlpgRUvGkafT5vKN5tT07BpPh6
+ry3cwSEpMaQQmq5CT57hf92k5A2idEh/u1YDNGnIrRRTLIrRwRucSoVfgrxpHbFg
+q9p5bL6RkjpIm1L5ytS6gFF0Bt+/QuIt82MCfTjCykavI4YfO6qkewA/4aoEecJ0
+z0QAflg8sJcpEFTiRtnMTRvFqfjYQcMTgZDBS7zaFgsZbqc/coOf/uozBzBqob8v
+PBDeiSC4Hp/a/Gy5vw+ADJgQ5OAwcp68KdBN5EmSU1S+xqyKEtKAr3CKin/+e0kq
+yV+2jaR+jBcPveZK89MEpEMxIsGIeSZh4OYkc7bS7iPO+Euafmek5uSbhlpejUBy
+2gOAj+W7HK2mpte8rWWEueVaAOj+bFd5VNgt2s7LS3D6jy3nzp4eAl9PI+K4Yaiy
+y4P2GVIyRESj2n4OlBdVMoyJAk4EHwEKADgWIQSsUw1SDy8yafXpgxOkhEkESq1c
+XQUCYAQctRcMgAH7+r21QbXclVvZum7bFs9bsSUlxAIHAAAKCRCkhEkESq1cXRDG
+D/0bkA471LRZzYURNP3oAITwEy/6NKcVY3EAPe6gQVMtOI03qQU8nSLG50yNHlLE
+TfN7zDFOWUAbgNqnss7fP3HUsrZ/XUbuathnkTQyVcmQfGYOjTXQI21YsUmwXUsb
+m8AHCKToxBpIe+Z0nSlqjJJg60GK0d2g19IgE4kji4575BUCFDUypkYNh5v6/0zZ
+4vriomRfeHmZ9ne+XkQ0kujjpvpy6LIhb7a3ckC/X5QrjGspyPeQN8oYfZZrvyo5
+JmbOI7XgiCmNTGIJP7C0l0UEMufkmCvoetbhlj6pUWJBsCHGbZgVYuD8hWmLoAUz
+p5EjWjMVERpHncI3TPevlwqZczUoDYsIKGMqrowzZj88PdWHWlyq6dvXTMlCUufF
+ZzPDsCjC6vPxhKdwUq0Nj3oV4HXfEHydC3XHmaFv3oglLGSqQ9VuCnzvpNnH0MRI
+FxBKFUR5J8rBjNXDNN3UtXkf927e/l7JyYIJ5XaHUzlTK53FEZRPeeJckyEd6NIr
+rm/BC73/3mMuAQZ4033PeY3qD+uZNWp8Epfs6idRJstkGzK8tlOyfT3L/MkcBYXB
+VEiyH7MUy2SbEjTgC40FtrmGP0YpmZ4MPj7pUymFps/eLRQBACnHum12k2fxxbyO
+80DWge8oMRHQMBr+TnFgGV18HmjYcq14LhchnhYC1eE0IokCTgQfAQoAOBYhBKxT
+DVIPLzJp9emDE6SESQRKrVxdBQJgBBy1FwyAAYyCPe0QqoBBY54SEFrOjW4MFKRw
+AgcAAAoJEKSESQRKrVxdfBUP/RAFKP+TbfOLzFeK9oNDABJIztB1xXXoqMyPUoLq
+qv1AEdgtu1qvvkPiaqBLYCHmA/sm7A9+p4lxnlYC38ahxMJhcZ/QXhaQaEOU336W
+fsNcu4Ir/4ST3hUwsFtxluSEd89/IfFiIs53ZpTtrH88nxJKoXa+U84WT6xP9OHW
+5nvvH5bLveQCpDZCkW/Q2RkbHMnlPaXHAe7nLS8S2Lgy4St3ldVZzKDC/zhBVnWa
+UPuFGmDQnImzwpklFnAXFYTRJ6CX2nDw02Vu20NA+V3b64V0BIdgb0Ylkit5R2hN
+5gmXUCXdftzv302szwhMF47NqPZ4T14kSwLh7LtDiYioDJxmnYvG1hxCu/cA1UeD
+xtTxA6tksz+QM8g+bN8ULTDfoNUX2ZFTyk+eF+J5calR6A06mxmHGOfc/dbEy4r+
+ztmSTnrfaPhCiHSBvCYtU7Q7GCa5EKVw9FtUJhY7oNrr15AQFrK5EJN3nIxZxHQD
+ocAv7e77jBGzUsh3r0DVOJlHX7Vjh3VcmgEh5P0vb3vZGFOgSdL9mZ/kuZAdJv8z
+JlSlShBT+P0zTicl7EGzLSx/sZtGi98TrOIqrqgBEPIJn3QokiIxVSeGfQtz9nrv
+kV7uvUMe5ABRu6mxNzc6JHtA7VZNMLFhp5imBKneMq5qksmTXkpb9bw7IoEPB1dd
+pBFXiQJOBB8BCgA4FiEErFMNUg8vMmn16YMTpIRJBEqtXF0FAmAEHLUXDIABMJkR
+vqlm0GEwUwRXEbTl/xWw/YICBwAACgkQpIRJBEqtXF2LAQ//dC9eL4nDDmW2YRZE
+xS5cgbMCYTeGkCUrMcL75px8HaNASxAWyUGxouT6XbiyCvIZRmyAEsLYOm1txIVy
+ddnHvH7v9HwRh08ystodyXqXTPnluHppVelQPIG071LLpyM1VM8qwrT3twdP7zXH
+WRzPwbUO2C8U9Fu6wiZCZb4Zcooldqj79487XKjPKws7f3gdkVYR7U3rwrfd0By3
+QSMlyh8aWe3YehU/zZ6MdxFIrAkHF0a9mrDRINy6BOtEc0ThBk5n/q8f7zxqf3No
+w9M8luok+eoVjXcAjrqHIY7rZ3TbCzV9e5OFoGHlsL1WieqxpZMmbS0UN2HGTyB/
+MpAJkYh1cB1nLNVOUnlOwjdM0PoKpdxtfUK3mtOuoB0TTCWwhi1FBI6oDYvbuMH4
+HOuvFqhGMiYmXC6Ln/eCVimWsnd0PsvrfomvJEZ2lFZzKw8QDOT4Z8xnopcVwuMq
++JbAyVRCsXpqloybMntB4SRQ/JwMf9+evnVh7hQWg6B32FhAjoOBRJTX6DxXYB8n
+qDVTh1iRUP3jO75rOiiYzgsfjDcDVO8+a4Cd8lySNvjMvpyKkjNs9pymkuTJwW1i
+WteZw71pdjRIUSd3o/7zOX08+saPakU/FT5E9xYANR4ZxR+iSHckgYJbiVYvrlE6
+LyZ7Ycty/fhhnLJ/92sDCj6wHkyJAk4EHwEKADgWIQSsUw1SDy8yafXpgxOkhEkE
+Sq1cXQUCYAQctRcMgAHHT2rJ6TOzBn9S8z+kWexnFbBwXwIHAAAKCRCkhEkESq1c
+XaYeD/4mxXBxPtjNaet+/3FvwO8h4G6nUuN5PqciXdeOpXKJWX+Rb4MZ0GhUxpie
+vAW0JCZHzqFKTUfAEWuhQOYkTFAxINA6G48bdFtyDmAYiRGrGKglPcYWKEF9EjDf
+rDhL0a5Adbg6ICtA21e8Y/VVSkl5uHFsjwPgjWmYKyvSw45sUT99Iv8JztkbbJVV
+oPSq55rXFasiDSN6RdsDX10ZNBA6ci6uSq3low3bKaNjkTHHrahat47MGh9YdCdm
+HvWPI44FlvJNGb9UGFG3I3pKSxQbntS2Vb6WGeXrA1hCMksnApoWIkBHytTBOSUn
+owrCXh2aY+w2PxWZGs6RJTsX/41rpWyS9LmOEf+rtes6vPk9D3mGbkv/puRZli2R
+lPwqsSi4nHegb7ajtbLuOFUHXGi8LSFVYvD/8YxrS02pwsrXlub6v/HffyFMg4rX
+zKsPaWv+Q54seXjIw1K1kaNdPTDC3sTuKKr8zzumDGrWYxOLmtzOwBy4XiQ0RJ9N
+lsJlNBcyY7P6cSX1pJumrTZMD5cmOCHf+qYHRkWIjfdgB20kx/vBgutDpP8AQ5dA
+8kt1RjCGCRLfU9UEOytT8Hf4Kp7SK83Oi9E6Auex8vMMSczPGrWSkmeUxPJxuE4+
+5KYTRkcJMl4WKEmQAae0ni0WskXeO/3YujWC7n3ho6+UNoyLXLRSRGViaWFuIFNl
+Y3VyaXR5IEFyY2hpdmUgQXV0b21hdGljIFNpZ25pbmcgS2V5ICgxMS9idWxsc2V5
+ZSkgPGZ0cG1hc3RlckBkZWJpYW4ub3JnPokCVAQTAQoAPhYhBKxTDVIPLzJp9emD
+E6SESQRKrVxdBQJgBBywAhsDBQkPCZwABQsJCAcDBRUKCQgLBRYCAwEAAh4BAheA
+AAoJEKSESQRKrVxdiXQP/383ukp2BKlwoMBJacl3CV6wB2iui8BA19MOmlxQd3f/
+V7/3sQBf+4J8H+SUFjJS4x3xBCOGn8u4k08BLTDEMr0ec8edEmhR2v/eMTzU0R2t
+5N7VWnapPf0H6vQbR3njwwmf7Xh6V+UiLUQIgb2ORq+35rg+I2pDgPUfKv++4jTz
+i+V3Xupb2ZB9iWPC1uRCmEOzpXb9DSDzANHnw2QbJ8a2KGMD3DHTuxV2uprQA01L
+IvRQrPQw7j6uDrIGjujwxMS8ut0mi7nDohiCgNwvujuzH9YeL40xLBqmJrB4UnHV
+2ZT4uQKH07jOs/N38+BH1Bl4qtSgyGmbUkN+P6MP73CWWHtWsJG2yG4WfRHteNkz
+Wi4MqBTQJlQm1l1/JdvbRdw7NIvbDSAYbVy7dhHmWFiR70FY6xHmlmUWA3QyrdP6
+Fu6DvxZjxCPCui3Mp2qzt18Zb0Ktz22tw2Gip1TI5bfqK2e5NcUWylNfsoo7J4i+
+MK1/zXbKjGNkB8WiNHpc2VZ64njshuBWxuL4oibgUTi2aAD4rNVRfRtchq7ZdGnz
+HqB9FyflAohS03npF2Va4tjx+mzRi7b/QekpdG6gREu5r+29m5togJKG28821Pid
+DZdH+dd8cotFlNgBMBu/zbOuuk/jPZb9GBLafC/jsR4hkIwHRh2mr+pnrFWxYkfa
+iQIzBBABCgAdFiEEgNFYI7f9FWH597zd3DDXwjy7q+4FAmAEHvEACgkQ3DDXwjy7
+q+7vFRAAoFGxubvIG1tmdrL3u6bzVs4DaCd6yomZru3EgZB0oJheNH1Howqai7LW
+kff4qzDbaz4CGFWXup5aXya2IBbX8CESUDI444aHC185bQfWITqFd84Dhj2isf8G
+6GwxwrbBQcG3LoVDepArzyBidmeB4QtpaE+lWX5TzLwzUEpFcxzvlsfTDtwiWe7j
+huZ+dWLbva3xRHoeXRgDrPVakwZOJ2cvTatgfPJt1EoEGmYkOlL26luFVtaY7vAO
+aJQxraqAyiOMbefEgKQmbvbwKc6lF4IQWyZoKillzofdlrKHAjo7jsranOCy4NiZ
+z6jXsWc9WoBQBXu0uidVmSTwOum8LQGDo8v+e+2A1yMAz6UIFBwNw3FFwwZNsscw
+Zfjo1EZQt0xcL5B0Ufr5pibclfVnBFUPt8c1yxjnULQKL4fvJgk07Tk1hqlTwq/q
+pzgDkJJPWK8j7h0RB4qfsQW+hF94QZJtEQy9pL1UjNj6k3ngjB/OXqc+cV7p4wRP
+tiqp8BqQPJLsnvbdcS1SUDdML9YafU3J3vj8yRtWqkcQ1gFNCaynzEwZc39IuRwe
+SbpYkfucj70m+9BXMO/wXdgh1GjBmavciFCTefEEHdpprAnMdy27Ps0r0Xidv6wv
+X9XX2cZbISj51y59bM1+NKYmOzNthFl7VE9SxcwrmL4s5HsmExWJAjMEEAEKAB0W
+IQQfiZg+AIH94BjzzJZzpPJ7jdR5NgUCYAQfrgAKCRBzpPJ7jdR5NrkrD/9aw4QP
+W2rnetBXRacMQ1VC9GKwZRJgW/5BvpWhtgEWnekxB9KLMmeDJIau/E86hhl4rnt8
+cPZbtiZEAi/pl8nRV3r6aL9M2Umv/7wxbkX7mdLJCYUuyJa5lHd6uEDV6t2FCSC6
+wHV4DAnKfodIFgwS+Vq5l6+v2Y/1k+2d/oplTRE/3xW4Ae4D5hNE4MGLUGrb+PgB
+laxLTf560zDyxyy77LjPUbm/a7Ud6cpMCI2nF7kZm9l6IU0lMsxTn+tFLgcxn24Q
+0LASHUvN1YbhTax6OX3FBNRNhzYDKiQX5o/6Qwq3kms3Mam3gLLZ2ntcX+jg1bLf
+XeCEN/YAVDGT3dTKYhFblHzUfF0QltnklkrI7kOJ1gzGY8xnrTWdUCfPbzzJqdRE
+crEYro9BW/QKbwELKsUIpN1lj6U/P7BmQuaU/W2UQY5ll6XlSOsRHfz0mOnYMNds
+E6Cqr6hQLRrhiK3c5r33lJXs78pHKvgfREGOkwGOwQ8zzHd843S2xC+4nROOV/Eb
+Q7b1BzchsJbkcNMpTBKaXMY1wMJZFuy6raP3QO518OjYkIampPdpLBvHia+hsLkO
+12A7eS134Swlrg0YLTETyeRQqURLYzyghKtRVTwhZFaQKKzPp4Jp+qkc2SDhXqNZ
+qYT20FhxLBH2Bz5oNzaVw0WfEjw6NrjW3RKazokCMwQQAQoAHRYhBF5hshcmXamA
+eiPF/036snDKqW36BQJgBB8HAAoJEE36snDKqW36VYkP/1EUlC8KnR/dj583OI7E
+Vgz7ezIOi2HKeNTFrnxPIpQfnfnaJZgFzfYkpo/0kPlLdQvWvqao5xKChtVmePX6
+ZPWnSlMQJvlv7879EpakYbHWhGibTBR4FHDrEVuJ77s9uOaA5q29fnAwYfvLkwHP
+ijgJWbYblTpctFQCswqzBKXHQ/AvTDUj2II0BFUAeeBTrMX4Lysq1auKO6lvlBhv
+t1/XYFSpN0b336+7vCEmqTru6zQHx2ctjS1POWYMvD3rjPetS9luQ/00lp+gzpAh
+X1yb877YSTX2k5F/lW0NO7+EpqzE9j/9D8XtyaMFh9WnFKlkNs0lnT4HrptNsNvy
+M9gx4NJZQ74E/o+DXzYI+zrrhqkljrg2GnTqSDcHc5kUak1VFNjDOHHTkSH/J9sz
+NG6BvdhsbSow70+gh1vT/8G7b5nNG2TVEaNP+DHNBbJ22GdCOh6TZX6LfdeodbIa
+JTSvPdqOVCWt1XjzpHihGiggJWQncp5fJ3SQFpk93exw344mYe6/YZNh4TrC1Egu
+fGruUVOVzt5TZRqekmhxLCg9ZfI5MG8mRnK3Ecqf7PiIAFcmZHbJvmIUBzgVL4KW
+mE3vBGeg4C8v4lYlmvf1IQLhtA7YBnPg+3ZhZYJQWVB6l9rUjvFlYwkv1ACSmsxU
+5Bc/2zLZZJ15KawtClrADrHJiQIzBBABCgAdFiEEgOl28UpQikjpyj/pvDciUsoc
++WQFAmAEIqIACgkQvDciUsoc+WSJ9A//VO7mREA0FDDTx4IUXET7AB71wHBRY+yN
+tF8zgllXXqiOobVoHSCjZKMYjFhe5qKv3n1/kR0AxxbPWBBwfutKFFoiRgt+SSB3
+iuaWxJ6jm6znBZUn9ys1t+Y1xydKLDHdoYyHhtg6vrQhs/gKwBMX/ccGVxD4h2el
+jbp66YTSByoSRGjs4efeYemelIsgrwP/Ap3iNrYdPjh/uBP3XTNQEkDqHzyVTFeM
+FIkvonNQQsAEgl3QcP+MWq+KBBozjqtgDAoiF0JAaVArayKW+eExDBUZXr+y7DS5
+v2nulAfQiZzVB4q39mMfYCoj6mCyLBZsx6Xosg8K0rh46PuQb/0TasrYV44bjRF9
+SBVaOBW8eqWRc66y6OHUX1a5KkOxsDt03gHKYWC+NBfeck37xbL9J6d5QsA0yNdb
+CYvfKqp7i6mLPaa+u+2zBk1Pp9aNgbWzx5CFZxRjqUFf/drxao+9jVKzn+cE9XtK
+ouzy19OShdZe3SyLFTvXLdQj7emJN/JUIUgo8BrJYNzsAOo+UVCbliVr0dU0i1JQ
+Of7k8iNdNWOYcUZq8sCRbQ9IJnnfVVX4OqRFhM7yzxyGFBEzFGu0h+hc+sDK9zK1
+jl99oX49V+RSvCdic0P1EIA63f8doHuUysIigiTyaQkXMfJGoR9uKm1F4+7hd/xf
+i5lXJuez5lOJAlUEEgEKAD8WIQT7+r21QbXclVvZum7bFs9bsSUlxAUCYAQy6yEa
+aHR0cDovL2dwZy5nYW5uZWZmLmRlL3BvbGljeS50eHQACgkQ2xbPW7ElJcR0ZxAA
+n/yWrs1CCw7plbiHCLqv/fk61wuXTFAjqiF77CXvekZulGjr7eN96LeXkrPCJIai
+7rJ2D7x2g1wXUiWZMWKh8M71tVzgY4UV8/+eEJFV8GqmdrxmenaQFqvuAV6y/HV7
+cJw1EQCpsWC3sujjWUvOmc+rBrBhkfueLP5EqdMJ+wyaX5j1iT2SsxcMNb6rPLlf
+STafv2yqvnGhIFL+ZD916ke0tPpzUemjb2SrrTqj7l147pMv72H2KxR1wXjGgqQM
+joOys5bgTBbyzTUFDCbJc8o5c/HfeUnFDfTEvPzoxLL20mnQ/Mq1j6Za/LcEdJe0
+liVDzDSI3ng43huZLNOkT5LVLyNqedJ4M1bSI2/TSyTpHOTJVWSAUpH7706e2Jom
+PWvKpwYU45WycXveviEfGaMWFxZpInzSWVZ2I0n8sH595sirFaEUF0vb7PYddh3O
+sTa/UwsccIQnxgzNqy+o/SqlHwR9nSMkvg6V3T0f4mvqRfiJfqtiMhqnxMpe124s
+9M7II8ACu1fjYf6j0rcwlhreD0r598oOqjmdiIAXRtRtVbW9YSLd+G563RAcoSBg
+s4Xu7557i107UPYOKQbpuGzFhymtK1wBik8DilvkFrc51BGkUNjKq5eUZHrUFHjY
+dVXXJW+6JHzECm8qCywz7cwVNBhNzCD9RkyK/hlKh3y5Ag0EYAQcsAEQAOvMWEKq
+TinNyE7ii0xbHFEegMhYAkSdHm6kVDcDBu51I/rz6Yww94xvvTs819oLxPp1GCEw
+blnry3mD4NZ3vSeefzvK86BFX16tRUmAUP4qgE3PUKNFEWC38toGKFKOAqpEw9TC
+oCKzyTAQ7qj64jtweIW20KHJ8FpZL9JkoImZSLp2AVA7gmJl+aUWVAJ6TBBmmGGW
+Bl5St33nYXvlmoOC1CBWcW8qG0wGRh81ftQg0/klzGQElTWyU4CuPAhCnwYKccnO
+cOVPjcdp+rgvvJwc02/qX1WI3ZPJFOqr75il99cqreoSEmO6hJEL7GUUGcANoqqW
+UTe4SIYi3R97aqlOF9OyS9+o0Bufl0c9TZYDaRTJrIVs4D2jxJ2gaN49kztAifEN
+YfS+wzE64YtbgNOlR4XvERs3D+08vwigqATeyApfxRs/VH7g/G3LVcIBIYJCHdnS
+T/AglTcAQ7iWvwLlhFJ2aSYH0rpMVjBmSlTJmvqKHLI6wLnC22c5vATnNYzO0Sh9
+Nokz6nfUUjNJruZkbIIYC1Ohu+8aEuDLThirvwDR03VIWDeF3BhFQdkdKfkfZtzA
+Y8Lr7rWGxb3HpbR+slekC6dzclLj2g6be48zsE6Az2ek//mV1farAPejpmA2vC9B
+5indR9XKNntCKuFU2KRHCShhsw9xfQUIcOpLABEBAAGJBHIEGAEKACYWIQSsUw1S
+Dy8yafXpgxOkhEkESq1cXQUCYAQcsAIbAgUJDwmcAAJACRCkhEkESq1cXcF0IAQZ
+AQoAHRYhBO1UExKjPxEo8QscbFRAR2K7tuhTBQJgBBywAAoJEFRAR2K7tuhT5OAP
+/0oBtjjVGeU9BNWczY+/gxEhbBuoBG6+/d0M60fk6npZq2yccAIwbcJzh6pOBPPj
+bA0imRC38Fz2sZotO+Rt2eELT0QEcKDOlvhH/syj4R9+bDuIax22A9QtLnBpICrB
+Kt4LazuOC4LzYqScYZmoO4EWXNOZICO7lggL43ScLNerpE6Zj5nGHO+74wrIY93H
+UdoRROvtzdRO6n8+GU6XW12JacdeK/wk9hbD4Qqa5HOtNTxOD4ZjLPS0QuGdUy2f
+COfuUgYc55aCf6YJqysr/nX8188AyAKkPBd5TQ8QE2nOK+1BJC+/gitE4oSpP/oY
+WjxbfxJcviZUooNImD9cNV6cLrHF+vc4MMDzFqAQlWIuACkaA6as3sA5Ev6wdJyf
+TtF7RhI8B/V7HGGp0QUwhNy9HCvaVOq23cydK24V2zEjv1Qh8ak7yNggTviPQo73
+eVFoh2VIrW6GjTDyrkxRR1Fswh8IotRGvfl9+h8FNn+3bobIbtURIAxzzngjOMIA
+BG4+dLq+PHorctCSAXEf4F6qKgBSnHKcFRpusQjtzNdqRanfwA0p9lnN+8tLR4Dx
+1UuvQnbglg4eTy5pVvoTR37jVB/PhNnfVipn+aH4FsizShjI365Dvn9JRNQNYOHd
+yu2qAHBoSwldPTlTb6w9Wp7ONoXc7nC28mCQpV2FPhWdpiUP/1OJFCuVGxHrMc2Y
+BPdiMOajZmu5pqG2Gyt8jOqbyLVXH2Q5J9gRZcfEUd0EF7ZWa7/gjxsHkSldaOD4
+TqFL4WB+MH2+WQu+kXo6unCy0HF66LNa3LY4rELiukt22q5XBwDlaZ8DPM0oTEti
+eVXGsB6gGMDdktRRFF/um3jyht54zEv8MAXvwQeIMNVxPHBM4d1pSJq37tPZE2vf
+bPjHr9zzm2wKNSymR5+CXueXkphYG+dZ5qmkWnvs6kYyBNZPoxMu4ik4EKYt8sIC
+2HvvfdhUhax58gjSWMJART20eNFIim7cLBRpmo1+tH40M26KBhzvuh4EPX7WYUge
+7wXqqSIgko9C0FZCTJBqKik4zMtZO+2k3fjbuotHlV8ZkqRmxsxZvicQA7TbIl/0
+CBiEZDVLE7f3QzYdedaFtJFRtunFEF6ipr6BySo0vHbmDx5LWs1gsNvxxw6AY1uw
+S6LZh9v3LJPL3hzdkKVfDRZX+wJwHgfxC0JwNL4+uRAcJMAGwM/Z54nv3nOCfVlA
+NcZxJ0+LfH4+gE11hm12YLO3wyprn3MVx9Ou7bJYaPnsxyUo1fa+t18WpqM2JBkI
+JWAy6ZTZHPH5LCgq13simS7zaTnC94kGaAsljS4jATglXGYXKXTVlr0oUlNgYsNo
+TT9yj35WJXkuIZ7GV189g3gogTpu
+=Xxba
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/tox.ini b/tox.ini
index ddb8c88d..569d9936 100644
--- a/tox.ini
+++ b/tox.ini
@@ -47,16 +47,17 @@
# ansible == 9.x ansible-core ~= 2.16.0
# ansible == 10.x ansible-core ~= 2.17.0
# ansible == 11.x ansible-core ~= 2.18.0
+# ansible == 12.x ansible-core ~= 2.19.0
# See also
-# - https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html#ansible-core-support-matrix
+# - https://docs.ansible.com/ansible/devel/reference_appendices/release_and_maintenance.html#ansible-core-support-matrix
[tox]
envlist =
init,
py{27,36}-mode_ansible-ansible{2.10,3,4},
py{311}-mode_ansible-ansible{2.10,3,4,5},
- py{313}-mode_ansible-ansible{6,7,8,9,10,11},
+ py{313}-mode_ansible-ansible{6,7,8,9,10,11,12},
py{27,36,313}-mode_mitogen,
report,
@@ -80,12 +81,14 @@ deps =
ansible3: ansible~=3.0
ansible4: ansible~=4.0
ansible5: ansible~=5.0
+ # From Ansible 6 PyPI distributions include a wheel
ansible6: ansible~=6.0
ansible7: ansible~=7.0
ansible8: ansible~=8.0
ansible9: ansible~=9.0
ansible10: ansible~=10.0
- ansible11: ansible>=11.0
+ ansible11: ansible~=11.0
+ ansible12: ansible>=12.0.0b2
install_command =
python -m pip --no-python-version-warning --disable-pip-version-check install {opts} {packages}
commands_pre =
@@ -106,6 +109,9 @@ setenv =
NOCOVERAGE_ERASE = 1
NOCOVERAGE_REPORT = 1
PIP_CONSTRAINT={toxinidir}/tests/constraints.txt
+ # Superceded in Ansible >= 6 (ansible-core >= 2.13) by result_format=yaml
+ # Deprecated in Ansible 12 (ansible-core 2.19)
+ ansible{2.10,3-5}: DEFAULT_STDOUT_CALLBACK=yaml
# Print warning on the first occurence at each module:linenno in Mitogen. Available Python 2.7, 3.2+.
PYTHONWARNINGS=default:::ansible_mitogen,default:::mitogen
# Ansible 6 - 8 (ansible-core 2.13 - 2.15) require Python 2.7 or >= 3.5 on targets
@@ -118,6 +124,7 @@ setenv =
ansible10: MITOGEN_TEST_DISTRO_SPECS=debian10-py3 debian11-py3 ubuntu2004-py3
# Ansible 11 (ansible-core 2.18) requires Python >= 3.8 on targets
ansible11: MITOGEN_TEST_DISTRO_SPECS=debian11-py3 ubuntu2004-py3
+ ansible12: MITOGEN_TEST_DISTRO_SPECS=debian11-py3 ubuntu2004-py3
distros_centos: MITOGEN_TEST_DISTRO_SPECS=centos6 centos7 centos8
distros_centos5: MITOGEN_TEST_DISTRO_SPECS=centos5
distros_centos6: MITOGEN_TEST_DISTRO_SPECS=centos6