diff --git a/changelogs/fragments/84578-dnf5-is_installed-provides.yml b/changelogs/fragments/84578-dnf5-is_installed-provides.yml new file mode 100644 index 00000000000..f2760356099 --- /dev/null +++ b/changelogs/fragments/84578-dnf5-is_installed-provides.yml @@ -0,0 +1,2 @@ +bugfixes: + - "dnf5 - fix ``is_installed`` check for packages that are not installed but listed as provided by an installed package (https://github.com/ansible/ansible/issues/84578)" diff --git a/lib/ansible/modules/dnf5.py b/lib/ansible/modules/dnf5.py index 2eef580933e..eb340f0a3ff 100644 --- a/lib/ansible/modules/dnf5.py +++ b/lib/ansible/modules/dnf5.py @@ -378,10 +378,20 @@ def is_installed(base, spec): # 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) + # Disable checking whether SPEC is provided by an installed package. + # Consider following real scenario from the rpmfusion repo: + # * the `ffmpeg-libs` package is installed and provides `libavcodec-freeworld` + # * but `libavcodec-freeworld` is NOT installed (???) + # * due to `set_with_provides(True)` being default `is_installed(base, "libavcodec-freeworld")` + # would "unexpectedly" return True + # We disable provides only for this `is_installed` check, for actual installation we leave the default + # setting to mirror the dnf cmdline behavior. + settings.set_with_provides(False) except AttributeError: # dnf5 < 5.2.0.0 settings.group_with_name = True settings.with_binaries = False + settings.with_provides = False installed_query = libdnf5.rpm.PackageQuery(base) installed_query.filter_installed() diff --git a/test/integration/targets/dnf/tasks/repo.yml b/test/integration/targets/dnf/tasks/repo.yml index cdec5a85ae7..6e1b78252ff 100644 --- a/test/integration/targets/dnf/tasks/repo.yml +++ b/test/integration/targets/dnf/tasks/repo.yml @@ -587,3 +587,35 @@ - provides-binary - package-name state: absent + +# https://github.com/ansible/ansible/issues/84578 +- name: Test installing a package that is listed in `provides` in different package + block: + - dnf: + name: provides-package + state: present + + - command: rpm -q provided-package + ignore_errors: true + register: r + + - assert: + that: + - r is failed + + - dnf: + name: provided-package + state: present + register: r + + - assert: + that: + - r is changed + always: + - name: Clean up + dnf: + name: "{{ item }}" + state: absent + loop: + - provides-package + - provided-package 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 6fffe5ad90b..ff1954313e0 100644 --- a/test/integration/targets/setup_rpm_repo/library/create_repo.py +++ b/test/integration/targets/setup_rpm_repo/library/create_repo.py @@ -33,6 +33,7 @@ class RPM: requires: list[str] | None = None file: str | None = None binary: str | None = None + provides: list[str] | None = None SPECS = [ @@ -63,6 +64,8 @@ SPECS = [ 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'), + RPM(name='provides-package', version='1.0', provides=['provided-package']), + RPM(name='provided-package', version='1.0'), ] @@ -78,6 +81,9 @@ def create_repo(): for recommend in spec.recommends or []: pkg.add_recommends(recommend) + for provide in spec.provides or []: + pkg.add_provides(provide) + if spec.file: pkg.add_installed_file( "/" + spec.file,