mirror of
https://github.com/ansible/ansible.git
synced 2026-08-04 16:13:32 +02:00
dnf: utilize the API for the installed checks (#82725)
Fixes #71808 Fixes #76463 Fixes #81018
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
bugfixes:
|
||||||
|
- Mirror the behavior of dnf on the command line when handling NEVRAs with omitted epoch (https://github.com/ansible/ansible/issues/71808)
|
||||||
|
- Fix NEVRA parsing of package names that include digit(s) in them (https://github.com/ansible/ansible/issues/76463, https://github.com/ansible/ansible/issues/81018)
|
||||||
+14
-125
@@ -387,7 +387,6 @@ EXAMPLES = '''
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ansible.module_utils.common.text.converters import to_native, to_text
|
from ansible.module_utils.common.text.converters import to_native, to_text
|
||||||
@@ -479,94 +478,6 @@ class DnfModule(YumDnf):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
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 = [
|
|
||||||
"aarch64", "alphaev56", "alphaev5", "alphaev67", "alphaev6", "alpha",
|
|
||||||
"alphapca56", "amd64", "armv3l", "armv4b", "armv4l", "armv5tejl", "armv5tel",
|
|
||||||
"armv5tl", "armv6hl", "armv6l", "armv7hl", "armv7hnl", "armv7l", "athlon",
|
|
||||||
"geode", "i386", "i486", "i586", "i686", "ia32e", "ia64", "m68k", "mips64el",
|
|
||||||
"mips64", "mips64r6el", "mips64r6", "mipsel", "mips", "mipsr6el", "mipsr6",
|
|
||||||
"noarch", "pentium3", "pentium4", "ppc32dy4", "ppc64iseries", "ppc64le", "ppc64",
|
|
||||||
"ppc64p7", "ppc64pseries", "ppc8260", "ppc8560", "ppciseries", "ppc", "ppcpseries",
|
|
||||||
"riscv64", "s390", "s390x", "sh3", "sh4a", "sh4", "sh", "sparc64", "sparc64v",
|
|
||||||
"sparc", "sparcv8", "sparcv9", "sparcv9v", "x86_64"
|
|
||||||
]
|
|
||||||
|
|
||||||
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
|
|
||||||
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()
|
|
||||||
if not version or not version.split('.')[0].isdigit():
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
except AttributeError as e:
|
|
||||||
self.module.fail_json(
|
|
||||||
msg='Error attempting to parse package: %s, %s' % (packagename, to_native(e)),
|
|
||||||
rc=1,
|
|
||||||
results=[]
|
|
||||||
)
|
|
||||||
|
|
||||||
if not epoch:
|
|
||||||
epoch = "0"
|
|
||||||
|
|
||||||
if ':' in name:
|
|
||||||
epoch_name = name.split(":")
|
|
||||||
|
|
||||||
epoch = epoch_name[0]
|
|
||||||
name = ''.join(epoch_name[1:])
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'name': name,
|
|
||||||
'epoch': epoch,
|
|
||||||
'release': release,
|
|
||||||
'version': version,
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
# Original implementation from yum.rpmUtils.miscutils (GPLv2+)
|
|
||||||
# http://yum.baseurl.org/gitweb?p=yum.git;a=blob;f=rpmUtils/miscutils.py
|
|
||||||
def _compare_evr(self, e1, v1, r1, e2, v2, r2):
|
|
||||||
# return 1: a is newer than b
|
|
||||||
# 0: a and b are the same version
|
|
||||||
# -1: b is newer than a
|
|
||||||
if e1 is None:
|
|
||||||
e1 = '0'
|
|
||||||
else:
|
|
||||||
e1 = str(e1)
|
|
||||||
v1 = str(v1)
|
|
||||||
r1 = str(r1)
|
|
||||||
if e2 is None:
|
|
||||||
e2 = '0'
|
|
||||||
else:
|
|
||||||
e2 = str(e2)
|
|
||||||
v2 = str(v2)
|
|
||||||
r2 = str(r2)
|
|
||||||
rc = dnf.rpm.rpm.labelCompare((e1, v1, r1), (e2, v2, r2))
|
|
||||||
return rc
|
|
||||||
|
|
||||||
def _ensure_dnf(self):
|
def _ensure_dnf(self):
|
||||||
locale = get_best_parsable_locale(self.module)
|
locale = get_best_parsable_locale(self.module)
|
||||||
os.environ['LC_ALL'] = os.environ['LC_MESSAGES'] = locale
|
os.environ['LC_ALL'] = os.environ['LC_MESSAGES'] = locale
|
||||||
@@ -575,7 +486,6 @@ class DnfModule(YumDnf):
|
|||||||
global dnf
|
global dnf
|
||||||
try:
|
try:
|
||||||
import dnf
|
import dnf
|
||||||
import dnf.cli
|
|
||||||
import dnf.const
|
import dnf.const
|
||||||
import dnf.exceptions
|
import dnf.exceptions
|
||||||
import dnf.package
|
import dnf.package
|
||||||
@@ -813,43 +723,22 @@ class DnfModule(YumDnf):
|
|||||||
self.module.exit_json(msg="", results=results)
|
self.module.exit_json(msg="", results=results)
|
||||||
|
|
||||||
def _is_installed(self, pkg):
|
def _is_installed(self, pkg):
|
||||||
installed = self.base.sack.query().installed()
|
return bool(
|
||||||
|
dnf.subject.Subject(pkg).get_best_query(sack=self.base.sack).installed().run()
|
||||||
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
|
|
||||||
|
|
||||||
return bool(installed.filter(**package_spec))
|
|
||||||
|
|
||||||
def _is_newer_version_installed(self, pkg_name):
|
def _is_newer_version_installed(self, pkg_name):
|
||||||
candidate_pkg = self._packagename_dict(pkg_name)
|
try:
|
||||||
if not candidate_pkg:
|
if isinstance(pkg_name, dnf.package.Package):
|
||||||
# The user didn't provide a versioned rpm, so version checking is
|
available = pkg_name
|
||||||
# not required
|
else:
|
||||||
return False
|
available = sorted(
|
||||||
|
dnf.subject.Subject(pkg_name).get_best_query(sack=self.base.sack).available().run()
|
||||||
installed = self.base.sack.query().installed()
|
)[-1]
|
||||||
installed_pkg = installed.filter(name=candidate_pkg['name']).run()
|
installed = sorted(self.base.sack.query().installed().filter(name=available.name).run())[-1]
|
||||||
if installed_pkg:
|
except IndexError:
|
||||||
installed_pkg = installed_pkg[0]
|
|
||||||
|
|
||||||
# this looks weird but one is a dict and the other is a dnf.Package
|
|
||||||
evr_cmp = self._compare_evr(
|
|
||||||
installed_pkg.epoch, installed_pkg.version, installed_pkg.release,
|
|
||||||
candidate_pkg['epoch'], candidate_pkg['version'], candidate_pkg['release'],
|
|
||||||
)
|
|
||||||
|
|
||||||
return evr_cmp == 1
|
|
||||||
else:
|
|
||||||
return False
|
return False
|
||||||
|
return installed > available
|
||||||
|
|
||||||
def _mark_package_install(self, pkg_spec, upgrade=False):
|
def _mark_package_install(self, pkg_spec, upgrade=False):
|
||||||
"""Mark the package for install."""
|
"""Mark the package for install."""
|
||||||
@@ -1000,7 +889,7 @@ class DnfModule(YumDnf):
|
|||||||
else:
|
else:
|
||||||
for pkg in pkgs:
|
for pkg in pkgs:
|
||||||
try:
|
try:
|
||||||
if self._is_newer_version_installed(self._package_dict(pkg)['nevra']):
|
if self._is_newer_version_installed(pkg):
|
||||||
if self.allow_downgrade:
|
if self.allow_downgrade:
|
||||||
self.base.package_install(pkg, strict=self.base.conf.strict)
|
self.base.package_install(pkg, strict=self.base.conf.strict)
|
||||||
else:
|
else:
|
||||||
|
|||||||
+29
-17
@@ -374,19 +374,37 @@ def is_newer_version_installed(base, spec):
|
|||||||
spec_nevra = next(iter(libdnf5.rpm.Nevra.parse(spec)))
|
spec_nevra = next(iter(libdnf5.rpm.Nevra.parse(spec)))
|
||||||
except (RuntimeError, StopIteration):
|
except (RuntimeError, StopIteration):
|
||||||
return False
|
return False
|
||||||
spec_name = spec_nevra.get_name()
|
|
||||||
v = spec_nevra.get_version()
|
spec_version = spec_nevra.get_version()
|
||||||
r = spec_nevra.get_release()
|
if not spec_version:
|
||||||
if not v or not r:
|
|
||||||
return False
|
return False
|
||||||
spec_evr = "{}:{}-{}".format(spec_nevra.get_epoch() or "0", v, r)
|
|
||||||
|
|
||||||
query = libdnf5.rpm.PackageQuery(base)
|
installed = libdnf5.rpm.PackageQuery(base)
|
||||||
query.filter_installed()
|
installed.filter_installed()
|
||||||
query.filter_name([spec_name])
|
installed.filter_name([spec_nevra.get_name()])
|
||||||
query.filter_evr([spec_evr], libdnf5.common.QueryCmp_GT)
|
installed.filter_latest_evr()
|
||||||
|
try:
|
||||||
|
installed_package = list(installed)[-1]
|
||||||
|
except IndexError:
|
||||||
|
return False
|
||||||
|
|
||||||
return query.size() > 0
|
target = libdnf5.rpm.PackageQuery(base)
|
||||||
|
target.filter_name([spec_nevra.get_name()])
|
||||||
|
target.filter_version([spec_version])
|
||||||
|
spec_release = spec_nevra.get_release()
|
||||||
|
if spec_release:
|
||||||
|
target.filter_release([spec_release])
|
||||||
|
spec_epoch = spec_nevra.get_epoch()
|
||||||
|
if spec_epoch:
|
||||||
|
target.filter_epoch([spec_epoch])
|
||||||
|
target.filter_latest_evr()
|
||||||
|
try:
|
||||||
|
target_package = list(target)[-1]
|
||||||
|
except IndexError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# FIXME https://github.com/rpm-software-management/dnf5/issues/1104
|
||||||
|
return libdnf5.rpm.rpmvercmp(installed_package.get_evr(), target_package.get_evr()) == 1
|
||||||
|
|
||||||
|
|
||||||
def package_to_dict(package):
|
def package_to_dict(package):
|
||||||
@@ -604,13 +622,7 @@ class Dnf5Module(YumDnf):
|
|||||||
for spec in self.names:
|
for spec in self.names:
|
||||||
if is_newer_version_installed(base, spec):
|
if is_newer_version_installed(base, spec):
|
||||||
if self.allow_downgrade:
|
if self.allow_downgrade:
|
||||||
if upgrade:
|
goal.add_install(spec, settings)
|
||||||
if is_installed(base, spec):
|
|
||||||
goal.add_upgrade(spec, settings)
|
|
||||||
else:
|
|
||||||
goal.add_install(spec, settings)
|
|
||||||
else:
|
|
||||||
goal.add_install(spec, settings)
|
|
||||||
elif is_installed(base, spec):
|
elif is_installed(base, spec):
|
||||||
if upgrade:
|
if upgrade:
|
||||||
goal.add_upgrade(spec, settings)
|
goal.add_upgrade(spec, settings)
|
||||||
|
|||||||
@@ -467,3 +467,56 @@
|
|||||||
state: absent
|
state: absent
|
||||||
loop:
|
loop:
|
||||||
- provides_foo_b
|
- provides_foo_b
|
||||||
|
|
||||||
|
- name: ensure that a package named "$str-$number-$str" is parsed correctly
|
||||||
|
block:
|
||||||
|
- dnf:
|
||||||
|
name: number-11-name-11.0
|
||||||
|
state: "{{ item }}"
|
||||||
|
loop:
|
||||||
|
- absent
|
||||||
|
- present
|
||||||
|
|
||||||
|
- dnf:
|
||||||
|
name: number-11-name
|
||||||
|
state: present
|
||||||
|
register: dnf_result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- dnf_result is not changed
|
||||||
|
|
||||||
|
- dnf:
|
||||||
|
name: number-11-name
|
||||||
|
state: latest
|
||||||
|
update_only: true
|
||||||
|
register: dnf_result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- dnf_result is changed
|
||||||
|
always:
|
||||||
|
- name: Clean up
|
||||||
|
dnf:
|
||||||
|
name: number-11-name
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
- name: test that epochs are handled the same way as via DNF on the command line
|
||||||
|
block:
|
||||||
|
- dnf:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: present
|
||||||
|
loop:
|
||||||
|
- "epochone-1.0-1.noarch"
|
||||||
|
- "epochone-1.1-1.noarch"
|
||||||
|
register: dnf_results
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- dnf_results["results"][0] is changed
|
||||||
|
- dnf_results["results"][1] is changed
|
||||||
|
always:
|
||||||
|
- name: Clean up
|
||||||
|
dnf:
|
||||||
|
name: epochone
|
||||||
|
state: absent
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ SPECS = [
|
|||||||
RPM('noarchfake', '1.0', '1', None, None, None, 'noarch'),
|
RPM('noarchfake', '1.0', '1', None, None, None, 'noarch'),
|
||||||
RPM('provides_foo_a', '1.0', '1', None, None, 'foo.so', 'noarch'),
|
RPM('provides_foo_a', '1.0', '1', None, None, 'foo.so', 'noarch'),
|
||||||
RPM('provides_foo_b', '1.0', '1', None, None, 'foo.so', 'noarch'),
|
RPM('provides_foo_b', '1.0', '1', None, None, 'foo.so', 'noarch'),
|
||||||
|
RPM('number-11-name', '11.0', '1', None, None, None, None),
|
||||||
|
RPM('number-11-name', '11.1', '1', None, None, None, None),
|
||||||
|
RPM('epochone', '1.0', '1', '1', None, None, "noarch"),
|
||||||
|
RPM('epochone', '1.1', '1', '1', None, None, "noarch"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user