diff --git a/.gitignore b/.gitignore index 9594499..ac4d604 100644 --- a/.gitignore +++ b/.gitignore @@ -85,5 +85,5 @@ /ansible-documentation-2.18.9.tar.gz /ansible-core-2.18.11.tar.gz /ansible-documentation-2.18.11.tar.gz -/ansible-core-2.20.1.tar.gz -/ansible-documentation-2.20.1.tar.gz +/ansible-core-2.18.12.tar.gz +/ansible-documentation-2.18.12.tar.gz diff --git a/0001-dnf5-apt-add-auto_install_module_deps-option-84292.patch b/0001-dnf5-apt-add-auto_install_module_deps-option-84292.patch new file mode 100644 index 0000000..340012a --- /dev/null +++ b/0001-dnf5-apt-add-auto_install_module_deps-option-84292.patch @@ -0,0 +1,348 @@ +From 9c9f20128efdf041a093f0de37fc316c6ec18b66 Mon Sep 17 00:00:00 2001 +From: Martin Krizek +Date: Thu, 21 Nov 2024 17:06:18 +0100 +Subject: [PATCH 1/2] dnf5,apt: add auto_install_module_deps option (#84292) + +* dnf5,apt: add auto_install_module_deps option + +Fixes #84206 + +(cherry picked from commit 2a53b851fee8ebaa07c1341122dd905354659237) +--- + ...4206-dnf5-apt-auto-install-module-deps.yml | 2 + + lib/ansible/modules/apt.py | 90 +++++++++++-------- + lib/ansible/modules/dnf5.py | 52 ++++++++--- + test/integration/targets/apt/tasks/apt.yml | 28 ++++-- + test/integration/targets/dnf5/playbook.yml | 21 ++++- + 5 files changed, 133 insertions(+), 60 deletions(-) + create mode 100644 changelogs/fragments/84206-dnf5-apt-auto-install-module-deps.yml + +diff --git a/changelogs/fragments/84206-dnf5-apt-auto-install-module-deps.yml b/changelogs/fragments/84206-dnf5-apt-auto-install-module-deps.yml +new file mode 100644 +index 0000000000..14d595449c +--- /dev/null ++++ b/changelogs/fragments/84206-dnf5-apt-auto-install-module-deps.yml +@@ -0,0 +1,2 @@ ++minor_changes: ++ - dnf5, apt - add ``auto_install_module_deps`` option (https://github.com/ansible/ansible/issues/84206) +diff --git a/lib/ansible/modules/apt.py b/lib/ansible/modules/apt.py +index 70a2a07cc0..c18bd36fe4 100644 +--- a/lib/ansible/modules/apt.py ++++ b/lib/ansible/modules/apt.py +@@ -17,6 +17,12 @@ description: + - Manages I(apt) packages (such as for Debian/Ubuntu). + version_added: "0.0.2" + options: ++ auto_install_module_deps: ++ description: ++ - Automatically install dependencies required to run this module. ++ type: bool ++ default: yes ++ version_added: 2.19 + name: + description: + - A list of package names, like V(foo), or package specifier with version, like V(foo=1.0) or V(foo>=1.0). +@@ -191,8 +197,7 @@ options: + default: 60 + version_added: "2.12" + requirements: +- - python-apt (python 2) +- - python3-apt (python 3) ++ - python3-apt + - aptitude (before 2.4) + author: "Matthew Williams (@mgwilliams)" + extends_documentation_fragment: action_common_attributes +@@ -214,8 +219,8 @@ notes: + - When used with a C(loop:) each package will be processed individually, it is much more efficient to pass the list directly to the O(name) option. + - When O(default_release) is used, an implicit priority of 990 is used. This is the same behavior as C(apt-get -t). + - When an exact version is specified, an implicit priority of 1001 is used. +- - If the interpreter can't import C(python-apt)/C(python3-apt) the module will check for it in system-owned interpreters as well. +- If the dependency can't be found, the module will attempt to install it. ++ - If the interpreter can't import C(python3-apt) the module will check for it in system-owned interpreters as well. ++ If the dependency can't be found, depending on the value of O(auto_install_module_deps) the module will attempt to install it. + If the dependency is found or installed, the module will be respawned under the correct interpreter. + ''' + +@@ -1233,6 +1238,7 @@ def main(): + allow_downgrade=dict(type='bool', default=False, aliases=['allow-downgrade', 'allow_downgrades', 'allow-downgrades']), + allow_change_held_packages=dict(type='bool', default=False), + lock_timeout=dict(type='int', default=60), ++ auto_install_module_deps=dict(type='bool', default=True), + ), + mutually_exclusive=[['deb', 'package', 'upgrade']], + required_one_of=[['autoremove', 'deb', 'package', 'update_cache', 'upgrade']], +@@ -1268,7 +1274,7 @@ def main(): + if not HAS_PYTHON_APT: + # This interpreter can't see the apt Python library- we'll do the following to try and fix that: + # 1) look in common locations for system-owned interpreters that can see it; if we find one, respawn under it +- # 2) finding none, try to install a matching python-apt package for the current interpreter version; ++ # 2) finding none, try to install a matching python3-apt package for the current interpreter version; + # we limit to the current interpreter version to try and avoid installing a whole other Python just + # for apt support + # 3) if we installed a support package, try to respawn under what we think is the right interpreter (could be +@@ -1294,39 +1300,47 @@ def main(): + + # don't make changes if we're in check_mode + if module.check_mode: +- module.fail_json(msg="%s must be installed to use check mode. " +- "If run normally this module can auto-install it." % apt_pkg_name) +- +- # We skip cache update in auto install the dependency if the +- # user explicitly declared it with update_cache=no. +- if module.params.get('update_cache') is False: +- module.warn("Auto-installing missing dependency without updating cache: %s" % apt_pkg_name) +- else: +- module.warn("Updating cache and auto-installing missing dependency: %s" % apt_pkg_name) +- module.run_command([APT_GET_CMD, 'update'], check_rc=True) +- +- # try to install the apt Python binding +- apt_pkg_cmd = [APT_GET_CMD, 'install', apt_pkg_name, '-y', '-q', dpkg_options] +- +- if install_recommends is False: +- apt_pkg_cmd.extend(["-o", "APT::Install-Recommends=no"]) +- elif install_recommends is True: +- apt_pkg_cmd.extend(["-o", "APT::Install-Recommends=yes"]) +- # install_recommends is None uses the OS default +- +- module.run_command(apt_pkg_cmd, check_rc=True) +- +- # try again to find the bindings in common places +- interpreter = probe_interpreters_for_module(interpreters, 'apt') +- +- if interpreter: +- # found the Python bindings; respawn this module under the interpreter where we found them +- # NB: respawn is somewhat wasteful if it's this interpreter, but simplifies the code +- respawn_module(interpreter) +- # this is the end of the line for this process, it will exit here once the respawned module has completed +- else: +- # we've done all we can do; just tell the user it's busted and get out +- module.fail_json(msg="{0} must be installed and visible from {1}.".format(apt_pkg_name, sys.executable)) ++ module.fail_json( ++ msg=f"{apt_pkg_name} must be installed to use check mode. " ++ "If run normally this module can auto-install it, " ++ "see the auto_install_module_deps option.", ++ ) ++ elif p['auto_install_module_deps']: ++ # We skip cache update in auto install the dependency if the ++ # user explicitly declared it with update_cache=no. ++ if module.params.get('update_cache') is False: ++ module.warn("Auto-installing missing dependency without updating cache: %s" % apt_pkg_name) ++ else: ++ module.warn("Updating cache and auto-installing missing dependency: %s" % apt_pkg_name) ++ module.run_command([APT_GET_CMD, 'update'], check_rc=True) ++ ++ # try to install the apt Python binding ++ apt_pkg_cmd = [APT_GET_CMD, 'install', apt_pkg_name, '-y', '-q', dpkg_options] ++ ++ if install_recommends is False: ++ apt_pkg_cmd.extend(["-o", "APT::Install-Recommends=no"]) ++ elif install_recommends is True: ++ apt_pkg_cmd.extend(["-o", "APT::Install-Recommends=yes"]) ++ # install_recommends is None uses the OS default ++ ++ module.run_command(apt_pkg_cmd, check_rc=True) ++ ++ # try again to find the bindings in common places ++ interpreter = probe_interpreters_for_module(interpreters, 'apt') ++ ++ if interpreter: ++ # found the Python bindings; respawn this module under the interpreter where we found them ++ # NB: respawn is somewhat wasteful if it's this interpreter, but simplifies the code ++ respawn_module(interpreter) ++ # this is the end of the line for this process, it will exit here once the respawned module has completed ++ ++ # we've done all we can do; just tell the user it's busted and get out ++ py_version = sys.version.replace("\n", "") ++ module.fail_json( ++ msg=f"Could not import the {apt_pkg_name} module using {sys.executable} ({py_version}). " ++ f"Ensure {apt_pkg_name} package is installed (either manually or via the auto_install_module_deps option) " ++ f"or that you have specified the correct ansible_python_interpreter. (attempted {interpreters}).", ++ ) + + if p['clean'] is True: + aptclean_stdout, aptclean_stderr, aptclean_diff = aptclean(module) +diff --git a/lib/ansible/modules/dnf5.py b/lib/ansible/modules/dnf5.py +index 24ae91432b..a0e4a4ef5a 100644 +--- a/lib/ansible/modules/dnf5.py ++++ b/lib/ansible/modules/dnf5.py +@@ -14,6 +14,12 @@ description: + provides are implemented in M(ansible.builtin.dnf5), please consult specific options for more information." + short_description: Manages packages with the I(dnf5) package manager + options: ++ auto_install_module_deps: ++ description: ++ - Automatically install dependencies required to run this module. ++ type: bool ++ default: yes ++ version_added: 2.19 + name: + description: + - "A package name or package specifier with version, like C(name-1.0). +@@ -246,6 +252,10 @@ attributes: + platforms: rhel + requirements: + - "python3-libdnf5" ++notes: ++ - If the interpreter can't import C(python3-libdnf5) the module will check for it in system-owned interpreters as well. ++ If the dependency can't be found, depending on the value of O(auto_install_module_deps) the module will attempt to install it. ++ If the dependency is found or installed, the module will be respawned under the correct interpreter. + version_added: 2.15 + """ + +@@ -474,6 +484,8 @@ def get_unneeded_pkgs(base): + class Dnf5Module(YumDnf): + def __init__(self, module): + super(Dnf5Module, self).__init__(module) ++ self.auto_install_module_deps = self.module.params["auto_install_module_deps"] ++ + self._ensure_dnf() + + self.pkg_mgr_name = "dnf5" +@@ -530,21 +542,30 @@ class Dnf5Module(YumDnf): + ] + + if not has_respawned(): +- # probe well-known system Python locations for accessible bindings, favoring py3 +- interpreter = probe_interpreters_for_module(system_interpreters, "libdnf5") +- +- if interpreter: +- # respawn under the interpreter where the bindings should be found +- respawn_module(interpreter) +- # end of the line for this module, the process will exit here once the respawned module completes ++ for attempt in (1, 2): ++ # probe well-known system Python locations for accessible bindings ++ interpreter = probe_interpreters_for_module(system_interpreters, "libdnf5") ++ if interpreter: ++ # respawn under the interpreter where the bindings should be found ++ respawn_module(interpreter) ++ # end of the line for this module, the process will exit here once the respawned module completes ++ if attempt == 1: ++ if self.module.check_mode: ++ self.module.fail_json( ++ msg="python3-libdnf5 must be installed to use check mode. " ++ "If run normally this module can auto-install it, " ++ "see the auto_install_module_deps option.", ++ ) ++ elif self.auto_install_module_deps: ++ self.module.run_command(["dnf", "install", "-y", "python3-libdnf5"], check_rc=True) ++ else: ++ break + +- # done all we can do, something is just broken (auto-install isn't useful anymore with respawn, so it was removed) ++ py_version = sys.version.replace("\n", "") + self.module.fail_json( +- msg="Could not import the libdnf5 python module using {0} ({1}). " +- "Please install python3-libdnf5 package or ensure you have specified the " +- "correct ansible_python_interpreter. (attempted {2})".format( +- sys.executable, sys.version.replace("\n", ""), system_interpreters +- ), ++ msg=f"Could not import the libdnf5 python module using {sys.executable} ({py_version}). " ++ "Ensure python3-libdnf5 package is installed (either manually or via the auto_install_module_deps option) " ++ f"or that you have specified the correct ansible_python_interpreter. (attempted {system_interpreters}).", + failures=[], + ) + +@@ -795,6 +816,11 @@ class Dnf5Module(YumDnf): + + + def main(): ++ yumdnf_argument_spec["argument_spec"].update( ++ dict( ++ auto_install_module_deps=dict(type="bool", default=True), ++ ) ++ ) + module = AnsibleModule(**yumdnf_argument_spec) + try: + Dnf5Module(module).run() +diff --git a/test/integration/targets/apt/tasks/apt.yml b/test/integration/targets/apt/tasks/apt.yml +index 64e00d3ca9..dda5fc1fab 100644 +--- a/test/integration/targets/apt/tasks/apt.yml ++++ b/test/integration/targets/apt/tasks/apt.yml +@@ -8,17 +8,17 @@ + distro_mirror: http://archive.ubuntu.com/ubuntu + when: ansible_distribution == 'Ubuntu' + +-# UNINSTALL 'python-apt' +-# The `apt` module has the smarts to auto-install `python-apt(3)`. To test, we +-# will first uninstall `python-apt`. +-- name: uninstall python-apt with apt ++# UNINSTALL 'python3-apt' ++# The `apt` module has the smarts to auto-install `python3-apt`. To test, we ++# will first uninstall `python3-apt`. ++- name: uninstall python3-apt with apt + apt: +- pkg: [python-apt, python3-apt] ++ pkg: python3-apt + state: absent + purge: yes + register: apt_result + +-# In check mode, auto-install of `python-apt` must fail ++# In check mode, auto-install of `python3-apt` must fail + - name: test fail uninstall hello without required apt deps in check mode + apt: + pkg: hello +@@ -32,13 +32,25 @@ + assert: + that: + - apt_result is failed +- - '"If run normally this module can auto-install it." in apt_result.msg' ++ - '"If run normally this module can auto-install it" in apt_result.msg' + + - name: check with dpkg +- shell: dpkg -s python-apt python3-apt ++ shell: dpkg -s python3-apt + register: dpkg_result + ignore_errors: true + ++- name: Test the auto_install_module_deps option ++ apt: ++ pkg: hello ++ auto_install_module_deps: false ++ register: r ++ ignore_errors: true ++ ++- assert: ++ that: ++ - r is failed ++ - r.msg is contains("Could not import the python3-apt module") ++ + # UNINSTALL 'hello' + # With 'python-apt' uninstalled, the first call to 'apt' should install + # python-apt without updating the cache. +diff --git a/test/integration/targets/dnf5/playbook.yml b/test/integration/targets/dnf5/playbook.yml +index d9a79ced7e..a36c17a202 100644 +--- a/test/integration/targets/dnf5/playbook.yml ++++ b/test/integration/targets/dnf5/playbook.yml +@@ -1,8 +1,27 @@ + - hosts: localhost + tasks: + - block: +- - command: dnf install -y python3-libdnf5 ++ - command: "dnf install -y 'dnf-command(copr)'" ++ - name: Test against dnf5 nightly build to detect any issues early ++ command: dnf copr enable -y rpmsoftwaremanagement/dnf-nightly + ++ - name: Ensure module deps are not installed ++ command: dnf remove -y python3-libdnf5 ++ ++ - name: Test the auto_install_module_deps option ++ dnf5: ++ name: sos ++ auto_install_module_deps: false ++ register: r ++ ignore_errors: true ++ ++ - assert: ++ that: ++ - r is failed ++ - r.msg is contains("Could not import the libdnf5 python module") ++ ++ # Now the first dnf5 task in the dnf role should auto install python3-libdnf5 as ++ # auto_install_module_deps is true by default. + - include_role: + name: dnf + vars: +-- +2.50.1 + diff --git a/0002-Initial-support-for-Python-3.14.patch b/0002-Initial-support-for-Python-3.14.patch new file mode 100644 index 0000000..8b71591 --- /dev/null +++ b/0002-Initial-support-for-Python-3.14.patch @@ -0,0 +1,182 @@ +From 36c1e6ff0d889cef5c57af64d4f6fc08b455bada Mon Sep 17 00:00:00 2001 +From: Maxwell G +Date: Sat, 7 Jun 2025 15:42:53 -0500 +Subject: [PATCH 2/2] Initial support for Python 3.14 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This is a downstream Fedora patch to make ansible-core work with Python +3.14 until it is properly supported upstream. +Note that parts of ansible-test will not work properly until we update +to an version that officially supports Python 3.14 due to missing +upstream test infrastructure. + +Co-authored-by: Miro Hrončok +--- + .../_data/requirements/ansible-test.txt | 2 +- + .../ansible_test/_internal/coverage_util.py | 2 +- + .../_util/target/common/constants.py | 1 + + .../module_utils/common/test_collections.py | 6 +- + .../test_check_required_arguments.py | 7 +- + test/units/modules/test_copy.py | 74 ++++++++++--------- + 6 files changed, 54 insertions(+), 38 deletions(-) + +diff --git a/test/lib/ansible_test/_data/requirements/ansible-test.txt b/test/lib/ansible_test/_data/requirements/ansible-test.txt +index 50f951c845..5edd5cc4de 100644 +--- a/test/lib/ansible_test/_data/requirements/ansible-test.txt ++++ b/test/lib/ansible_test/_data/requirements/ansible-test.txt +@@ -1,2 +1,2 @@ + # The test-constraints sanity test verifies this file, but changes must be made manually to keep it in up-to-date. +-coverage == 7.6.1 ; python_version >= '3.8' and python_version <= '3.13' ++coverage == 7.6.1 ; python_version >= '3.8' and python_version <= '3.14' +diff --git a/test/lib/ansible_test/_internal/coverage_util.py b/test/lib/ansible_test/_internal/coverage_util.py +index 2bec9c791e..e900ddb801 100644 +--- a/test/lib/ansible_test/_internal/coverage_util.py ++++ b/test/lib/ansible_test/_internal/coverage_util.py +@@ -69,7 +69,7 @@ class CoverageVersion: + + COVERAGE_VERSIONS = ( + # IMPORTANT: Keep this in sync with the ansible-test.txt requirements file. +- CoverageVersion('7.6.1', 7, (3, 8), (3, 13)), ++ CoverageVersion('7.6.1', 7, (3, 8), (3, 14)), + ) + """ + This tuple specifies the coverage version to use for Python version ranges. +diff --git a/test/lib/ansible_test/_util/target/common/constants.py b/test/lib/ansible_test/_util/target/common/constants.py +index 31f56adcda..4e4055462d 100644 +--- a/test/lib/ansible_test/_util/target/common/constants.py ++++ b/test/lib/ansible_test/_util/target/common/constants.py +@@ -14,4 +14,5 @@ CONTROLLER_PYTHON_VERSIONS = ( + '3.11', + '3.12', + '3.13', ++ '3.14', + ) +diff --git a/test/units/module_utils/common/test_collections.py b/test/units/module_utils/common/test_collections.py +index 381d583004..78d7d19dde 100644 +--- a/test/units/module_utils/common/test_collections.py ++++ b/test/units/module_utils/common/test_collections.py +@@ -5,6 +5,8 @@ + + from __future__ import annotations + ++import re ++ + import pytest + + from collections.abc import Sequence +@@ -143,7 +145,9 @@ class TestImmutableDict: + # ImmutableDict is unhashable when one of its values is unhashable + imdict = ImmutableDict({u'café': u'くらとみ', 1: [1, 2]}) + +- expected_reason = r"^unhashable type: 'list'$" ++ python314_reason = re.escape("cannot use 'tuple' as a set element (unhashable type: 'list')") ++ expected_reasons = (r"^unhashable type: 'list'$", rf"^{python314_reason}$") ++ expected_reason = "|".join(expected_reasons) + + with pytest.raises(TypeError, match=expected_reason): + hash(imdict) +diff --git a/test/units/module_utils/common/validation/test_check_required_arguments.py b/test/units/module_utils/common/validation/test_check_required_arguments.py +index 16e79fe7dc..761cd0acd1 100644 +--- a/test/units/module_utils/common/validation/test_check_required_arguments.py ++++ b/test/units/module_utils/common/validation/test_check_required_arguments.py +@@ -84,4 +84,9 @@ def test_check_required_arguments_missing_none(): + def test_check_required_arguments_no_params(arguments_terms): + with pytest.raises(TypeError) as te: + check_required_arguments(arguments_terms, None) +- assert "'NoneType' is not iterable" in to_native(te.value) ++ value = to_native(te.value) ++ options = ( ++ "'NoneType' is not iterable", # Python < 3.14 ++ "argument of type 'NoneType' is not a container or iterable" # 3.14+ ++ ) ++ assert any(o in value for o in options) +diff --git a/test/units/modules/test_copy.py b/test/units/modules/test_copy.py +index 6f15bed122..799ba4b52f 100644 +--- a/test/units/modules/test_copy.py ++++ b/test/units/modules/test_copy.py +@@ -95,40 +95,46 @@ ONE_DIR_DATA: tuple[tuple[str, tuple[str, list[str]] | None, tuple[str, list[str + ONE_DIR_DATA += tuple(item[:3] for item in TWO_DIRS_DATA) + + +-@pytest.mark.parametrize('directory, expected', ((d[0], d[4]) for d in THREE_DIRS_DATA)) +-@pytest.mark.xfail(reason='broken test and/or code, original test missing assert', strict=False) +-def test_split_pre_existing_dir_three_levels_exist(directory, expected, mocker): +- mocker.patch('os.path.exists', side_effect=[True, True, True]) +- assert split_pre_existing_dir(directory) == expected +- +- +-@pytest.mark.parametrize('directory, expected', ((d[0], d[3]) for d in TWO_DIRS_DATA)) +-@pytest.mark.xfail(reason='broken test and/or code, original test missing assert', strict=False) +-def test_split_pre_existing_dir_two_levels_exist(directory, expected, mocker): +- mocker.patch('os.path.exists', side_effect=[True, True, False]) +- assert split_pre_existing_dir(directory) == expected +- +- +-@pytest.mark.parametrize('directory, expected', ((d[0], d[2]) for d in ONE_DIR_DATA)) +-@pytest.mark.xfail(reason='broken test and/or code, original test missing assert', strict=False) +-def test_split_pre_existing_dir_one_level_exists(directory, expected, mocker): +- mocker.patch('os.path.exists', side_effect=[True, False, False]) +- assert split_pre_existing_dir(directory) == expected +- +- +-@pytest.mark.parametrize('directory', (d[0] for d in ONE_DIR_DATA if d[1] is None)) +-def test_split_pre_existing_dir_root_does_not_exist(directory, mocker): +- mocker.patch('os.path.exists', return_value=False) +- with pytest.raises(AnsibleModuleError) as excinfo: +- split_pre_existing_dir(directory) +- assert excinfo.value.results['msg'].startswith("The '/' directory doesn't exist on this machine.") +- +- +-@pytest.mark.parametrize('directory, expected', ((d[0], d[1]) for d in ONE_DIR_DATA if not d[0].startswith('/'))) +-@pytest.mark.xfail(reason='broken test and/or code, original test missing assert', strict=False) +-def test_split_pre_existing_dir_working_dir_exists(directory, expected, mocker): +- mocker.patch('os.path.exists', return_value=False) +- assert split_pre_existing_dir(directory) == expected ++# NOTE(gotmax23): These tests are all broken (marked with xfail) to begin with. ++# On Python 3.14, they also cause pytest to crash, as the os.path.exists patch ++# does not get cleaned up in time for some reason and other internal pytest ++# code calls the mock instead of the actual function. ++# Comment them out for now. ++ ++# @pytest.mark.parametrize('directory, expected', ((d[0], d[4]) for d in THREE_DIRS_DATA)) ++# @pytest.mark.xfail(reason='broken test and/or code, original test missing assert', strict=False) ++# def test_split_pre_existing_dir_three_levels_exist(directory, expected, mocker): ++# mocker.patch('os.path.exists', side_effect=[True, True, True]) ++# assert split_pre_existing_dir(directory) == expected ++# ++# ++# @pytest.mark.parametrize('directory, expected', ((d[0], d[3]) for d in TWO_DIRS_DATA)) ++# @pytest.mark.xfail(reason='broken test and/or code, original test missing assert', strict=False) ++# def test_split_pre_existing_dir_two_levels_exist(directory, expected, mocker): ++# mocker.patch('os.path.exists', side_effect=[True, True, False]) ++# assert split_pre_existing_dir(directory) == expected ++# ++# ++# @pytest.mark.parametrize('directory, expected', ((d[0], d[2]) for d in ONE_DIR_DATA)) ++# @pytest.mark.xfail(reason='broken test and/or code, original test missing assert', strict=False) ++# def test_split_pre_existing_dir_one_level_exists(directory, expected, mocker): ++# mocker.patch('os.path.exists', side_effect=[True, False, False]) ++# assert split_pre_existing_dir(directory) == expected ++# ++# ++# @pytest.mark.parametrize('directory', (d[0] for d in ONE_DIR_DATA if d[1] is None)) ++# def test_split_pre_existing_dir_root_does_not_exist(directory, mocker): ++# mocker.patch('os.path.exists', return_value=False) ++# with pytest.raises(AnsibleModuleError) as excinfo: ++# split_pre_existing_dir(directory) ++# assert excinfo.value.results['msg'].startswith("The '/' directory doesn't exist on this machine.") ++# ++# ++# @pytest.mark.parametrize('directory, expected', ((d[0], d[1]) for d in ONE_DIR_DATA if not d[0].startswith('/'))) ++# @pytest.mark.xfail(reason='broken test and/or code, original test missing assert', strict=False) ++# def test_split_pre_existing_dir_working_dir_exists(directory, expected, mocker): ++# mocker.patch('os.path.exists', return_value=False) ++# assert split_pre_existing_dir(directory) == expected + + + # +-- +2.50.1 + diff --git a/ansible-core.spec b/ansible-core.spec index 4df897f..27dfe08 100644 --- a/ansible-core.spec +++ b/ansible-core.spec @@ -13,21 +13,29 @@ %undefine _py3_shebang_s Name: ansible-core -Version: 2.20.1 +Version: 2.18.12 %global uversion %{version_no_tilde %{quote:%nil}} -Release: 2%{?dist} +Release: 1%{?dist} Summary: A radically simple IT automation system # The main license is GPLv3+. Many of the files in lib/ansible/module_utils # are BSD licensed. There are various files scattered throughout the codebase # containing code under different licenses. -# The ssh-agent helper code is BSD-3-Clause. -License: GPL-3.0-or-later AND BSD-2-Clause AND BSD-3-Clause AND PSF-2.0 AND MIT AND Apache-2.0 +License: GPL-3.0-or-later AND BSD-2-Clause AND PSF-2.0 AND MIT AND Apache-2.0 URL: https://ansible.com Source0: https://github.com/ansible/ansible/archive/v%{uversion}/%{name}-%{uversion}.tar.gz Source1: https://github.com/ansible/ansible-documentation/archive/v%{uversion}/ansible-documentation-%{uversion}.tar.gz +# dnf5,apt: add auto_install_module_deps option (#84292) +# https://github.com/ansible/ansible/pull/84292.patch +# https://bugzilla.redhat.com/2322751 +Patch: 0001-dnf5-apt-add-auto_install_module_deps-option-84292.patch +# Initial support for Python 3.14 +# Downstream patch. See comments in patch file. +# https://bugzilla.redhat.com/2366307 +Patch: 0002-Initial-support-for-Python-3.14.patch + BuildArch: noarch # Virtual provides for bundled libraries @@ -39,11 +47,14 @@ Provides: bundled(python3dist(distro)) = 1.9.0 # lib/ansible/module_utils/six/* # SPDX-License-Identifier: MIT -Provides: bundled(python3dist(six)) = 1.17.0 +Provides: bundled(python3dist(six)) = 1.16.0 -# lib/ansible/_internal/_wrapt.py -# SPDX-License-Identifier: BSD-2-Clause -Provides: bundled(python3dist(wrapt)) = 1.17.2 +Conflicts: ansible <= 2.9.99 +# +# obsoletes/provides for ansible-base +# +Provides: ansible-base = %{version}-%{release} +Obsoletes: ansible-base < 2.10.6-1 BuildRequires: make BuildRequires: python%{python3_pkgversion}-devel @@ -234,8 +245,8 @@ install -Dpm 0644 licenses/* -t %{buildroot}%{_pkglicensedir} %files -f %{pyproject_files} %license COPYING -%license %{_pkglicensedir}/{Apache-License,MIT-license,PSF-license,simplified_bsd,BSD-3-Clause}.txt -%doc README.md changelogs/CHANGELOG-v2.2?.rst +%license %{_pkglicensedir}/{Apache-License,MIT-license,PSF-license,simplified_bsd}.txt +%doc README.md changelogs/CHANGELOG-v2.1?.rst %dir %{_sysconfdir}/ansible/ %config(noreplace) %{_sysconfdir}/ansible/* %{_bindir}/ansible* @@ -254,14 +265,8 @@ install -Dpm 0644 licenses/* -t %{buildroot}%{_pkglicensedir} %changelog -* Fri Jan 16 2026 Fedora Release Engineering - 2.20.1-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_44_Mass_Rebuild - -* Tue Dec 09 2025 Maxwell G - 2.20.1-1 -- Update to 2.20.1. Fixes rhbz#2382388. -- Update bundled() Provides -- Remove upstreamed patches -- Remove old Provides and Obsoletes for ansible-base and Ansible <= 2.9 +* Thu Dec 11 2025 Maxwell G - 2.18.12-1 +- Update to 2.18.12. * Mon Nov 17 2025 Packit - 2.18.11-1 - Update to version 2.18.11 diff --git a/sources b/sources index 47c7d63..b75b8e1 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ -SHA512 (ansible-core-2.20.1.tar.gz) = fa0a4836e3548cd4e432e87b241beb6fb556765699c25b1f3b1c47111a1c44d5ba3244aeb8793408e72ab63564d6e848148becbfb550bd965e466752d7f78229 -SHA512 (ansible-documentation-2.20.1.tar.gz) = 0dc20cb62280c715e4b06788a5eb2c757c388d0da646a38fc3ab56e38d236ddb0fd7586a567d973e530ed3ed2310ff26542cdb0e1621e0049147dc747e20205b +SHA512 (ansible-core-2.18.12.tar.gz) = 9d276f2be553bdd30bc79bb1a4996f2c49f7c64eefc7926a8809b999c6023ed80cae189754caf203b0ebd90a8bb55de6c7489875dca10bad27cf5d944f006f7e +SHA512 (ansible-documentation-2.18.12.tar.gz) = 8dda1716f9abc5fa2370f21384a14fc665f21e703aca64c3961f75a9a3cea672a3f99f36dbb798000f8d3c9803e883f60a24d823ce1a6a187cdfbe6d0fdf16c1