diff --git a/changelogs/fragments/84259-dnf5-latest-fix.yml b/changelogs/fragments/84259-dnf5-latest-fix.yml new file mode 100644 index 00000000000..40f6ddb7408 --- /dev/null +++ b/changelogs/fragments/84259-dnf5-latest-fix.yml @@ -0,0 +1,2 @@ +bugfixes: + - "dnf5 - fix installing a package using ``state=latest`` when a binary of the same name as the package is already installed (https://github.com/ansible/ansible/issues/84259)" diff --git a/lib/ansible/modules/dnf5.py b/lib/ansible/modules/dnf5.py index df4ee206748..b157158514f 100644 --- a/lib/ansible/modules/dnf5.py +++ b/lib/ansible/modules/dnf5.py @@ -358,6 +358,15 @@ libdnf5 = None def is_installed(base, spec): settings = libdnf5.base.ResolveSpecSettings() + # Disable checking whether SPEC is a binary -> `/usr/(s)bin/`, + # this prevents scenarios like the following: + # * the `sssd-common` package is installed and provides `/usr/sbin/sssd` binary + # * the `sssd` package is NOT installed + # * due to `set_with_binaries(True)` being default `is_installed(base, "sssd")` would "unexpectedly" return True + # If users wish to target the `sssd` binary they can by specifying the full path `name=/usr/sbin/sssd` explicitly + # due to settings.set_with_filenames(True) being default. + settings.set_with_binaries(False) + installed_query = libdnf5.rpm.PackageQuery(base) installed_query.filter_installed() match, nevra = installed_query.resolve_pkg_spec(spec, settings, True) diff --git a/test/integration/targets/dnf/tasks/repo.yml b/test/integration/targets/dnf/tasks/repo.yml index 6ab8fa1a3b3..ec31fe4a4ae 100644 --- a/test/integration/targets/dnf/tasks/repo.yml +++ b/test/integration/targets/dnf/tasks/repo.yml @@ -517,3 +517,50 @@ dnf: name: provides_foo* state: absent + +# https://github.com/ansible/ansible/issues/84259 +- name: test installing a package named `package-name` while a package providing `/usr/sbin/package-name` is installed + block: + - dnf: + name: package-name + state: absent + + - dnf: + name: provides-binary + state: present + + - dnf: + name: package-name + state: latest + register: dnf_result + + - assert: + that: + - dnf_result is changed + always: + - name: Clean up + dnf: + name: + - provides-binary + - package-name + state: absent + +- name: test installing a package that provides a binary by specifying the binary name + block: + - dnf: + name: provides-binary + state: absent + + - dnf: + name: /usr/sbin/package-name + state: present + register: dnf_result + + - assert: + that: + - dnf_result is changed + always: + - name: Clean up + dnf: + name: provides-binary + state: absent diff --git a/test/integration/targets/setup_rpm_repo/library/create_repo.py b/test/integration/targets/setup_rpm_repo/library/create_repo.py index 5acf2397195..6fffe5ad90b 100644 --- a/test/integration/targets/setup_rpm_repo/library/create_repo.py +++ b/test/integration/targets/setup_rpm_repo/library/create_repo.py @@ -15,8 +15,10 @@ try: from rpmfluff.make import make_gif from rpmfluff.sourcefile import GeneratedSourceFile from rpmfluff.rpmbuild import SimpleRpmBuild + from rpmfluff.utils import expectedArch from rpmfluff.yumrepobuild import YumRepoBuild except ImportError: + expectedArch = None # define here to avoid NameError as it is used on top level in SPECS HAS_RPMFLUFF = False @@ -30,6 +32,7 @@ class RPM: recommends: list[str] | None = None requires: list[str] | None = None file: str | None = None + binary: str | None = None SPECS = [ @@ -58,6 +61,8 @@ SPECS = [ RPM(name='broken-b', version='1.0', requires=['broken-a = 1.2.3-1']), RPM(name='broken-c', version='1.0', requires=['broken-c = 1.2.4-1']), RPM(name='broken-d', version='1.0', requires=['broken-a']), + RPM(name='provides-binary', version='1.0', arch=[expectedArch], binary='/usr/sbin/package-name'), + RPM(name='package-name', version='1.0'), ] @@ -81,10 +86,13 @@ def create_repo(): ) ) + if spec.binary: + pkg.add_simple_compilation(installPath=spec.binary) + pkgs.append(pkg) repo = YumRepoBuild(pkgs) - repo.make('noarch', 'i686', 'x86_64') + repo.make('noarch', 'i686', 'x86_64', expectedArch) for pkg in pkgs: pkg.clean()