diff --git a/changelogs/fragments/fix-dnf-filtering-for-installed-package-name.yml b/changelogs/fragments/fix-dnf-filtering-for-installed-package-name.yml new file mode 100644 index 00000000000..f8a788a9920 --- /dev/null +++ b/changelogs/fragments/fix-dnf-filtering-for-installed-package-name.yml @@ -0,0 +1,2 @@ +bugfixes: + - dnf module - Use all components of a package name to determine if it's installed (https://github.com/ansible/ansible/issues/75311). diff --git a/lib/ansible/modules/dnf.py b/lib/ansible/modules/dnf.py index 8ffe61ad5c6..183036a9892 100644 --- a/lib/ansible/modules/dnf.py +++ b/lib/ansible/modules/dnf.py @@ -391,15 +391,7 @@ class DnfModule(YumDnf): return result - def _packagename_dict(self, packagename): - """ - Return a dictionary of information for a package name string or None - if the package name doesn't contain at least all NVR elements - """ - - if packagename[-4:] == '.rpm': - packagename = packagename[:-4] - + def _split_package_arch(self, packagename): # This list was auto generated on a Fedora 28 system with the following one-liner # printf '[ '; for arch in $(ls /usr/lib/rpm/platform); do printf '"%s", ' ${arch%-linux}; done; printf ']\n' redhat_rpm_arches = [ @@ -414,15 +406,26 @@ class DnfModule(YumDnf): "sparc", "sparcv8", "sparcv9", "sparcv9v", "x86_64" ] - rpm_arch_re = re.compile(r'(.*)\.(.*)') + name, delimiter, arch = packagename.rpartition('.') + if name and arch and arch in redhat_rpm_arches: + return name, arch + return packagename, None + + def _packagename_dict(self, packagename): + """ + Return a dictionary of information for a package name string or None + if the package name doesn't contain at least all NVR elements + """ + + if packagename[-4:] == '.rpm': + packagename = packagename[:-4] + rpm_nevr_re = re.compile(r'(\S+)-(?:(\d*):)?(.*)-(~?\w+[\w.+]*)') try: arch = None - rpm_arch_match = rpm_arch_re.match(packagename) - if rpm_arch_match: - nevr, arch = rpm_arch_match.groups() - if arch in redhat_rpm_arches: - packagename = nevr + nevr, arch = self._split_package_arch(packagename) + if arch: + packagename = nevr rpm_nevr_match = rpm_nevr_re.match(packagename) if rpm_nevr_match: name, epoch, version, release = rpm_nevr_re.match(packagename).groups() @@ -682,7 +685,20 @@ class DnfModule(YumDnf): def _is_installed(self, pkg): installed = self.base.sack.query().installed() - if installed.filter(name=pkg): + + package_spec = {} + name, arch = self._split_package_arch(pkg) + if arch: + package_spec['arch'] = arch + + package_details = self._packagename_dict(pkg) + if package_details: + package_details['epoch'] = int(package_details['epoch']) + package_spec.update(package_details) + else: + package_spec['name'] = name + + if installed.filter(**package_spec): return True else: return False diff --git a/test/integration/targets/dnf/tasks/filters.yml b/test/integration/targets/dnf/tasks/filters.yml index d5e9ee906b2..ce7c7e9bebd 100644 --- a/test/integration/targets/dnf/tasks/filters.yml +++ b/test/integration/targets/dnf/tasks/filters.yml @@ -98,6 +98,19 @@ allow_downgrade: true disable_gpg_check: true + - name: Verify toaster is not upgraded with state=installed + dnf: + name: "{{ item }}" + state: installed + register: installed + loop: + - toaster + - toaster.noarch + - toaster-1.2.3.4-1.el8.noarch + + - assert: + that: "[0, 1, 2] | map('extract', installed.results, 'changed') == [False, False, False]" + - name: Ask for pending updates with bugfix=true and security=true dnf: name: '*' @@ -118,6 +131,21 @@ - '"Installed: oven-1.2.3.5-1.el8.noarch" in update_bugfix.results' - '"Removed: oven-1.2.3.4-1.el8.noarch" in update_bugfix.results' + - name: Install old version of toaster again + dnf: + name: "{{ updateinfo_repo }}/toaster-1.2.3.4-1.el8.noarch.rpm" + allow_downgrade: true + disable_gpg_check: true + + - name: Verify toaster is upgraded with state=latest + dnf: + name: toaster.noarch + state: latest + register: result + + - assert: + that: result.changed + always: - name: Remove installed packages dnf: