mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
YUM4/DNF compatibility via yum action plugin (#44322)
* YUM4/DNF compatibility via yum action plugin DNF does not natively support allow_downgrade as an option, instead that is always the default (not configurable by the administrator) so it had to be implemented - Fixed group actions in check mode to report correct changed state - Better error handling for depsolve and transaction errors in DNF - Fixed group action idempotent transactions - Add use_backend to yum module/action plugin - Fix dnf handling of autoremove (didn't used to work nor had a default value specified, now does work and matches default behavior of yum) - Enable installroot tests for yum4(dnf) integration testing, dnf backend now supports that - Switch from zip to bc for certain package install/remove test cases in yum integration tests. The dnf depsolver downgrades python when you uninstall zip which alters the test environment and we have no control over that. - Add changelog fragment - Return a pkg_mgr fact if it was not previously set.
This commit is contained in:
parent
9ff20521d1
commit
397febd343
12 changed files with 826 additions and 247 deletions
22
changelogs/fragments/yum4_dnf_yum_action_plugin.yml
Normal file
22
changelogs/fragments/yum4_dnf_yum_action_plugin.yml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
major_changes:
|
||||||
|
- yum and dnf modules now at feature parity
|
||||||
|
- new yum action plugin enables the yum module to work with both yum3
|
||||||
|
and dnf-based yum4 by detecting the backend package manager and routing
|
||||||
|
commands through the correct Ansible module for that python API
|
||||||
|
- New yumdnf module defines the shared argument specification for both
|
||||||
|
yum and dnf modules and provides an entry point to share code when
|
||||||
|
applicable
|
||||||
|
|
||||||
|
minor_changes:
|
||||||
|
- Fixed group actions in check mode to report correct changed state
|
||||||
|
- Better error handling for depsolve and transaction errors in DNF
|
||||||
|
- Fixed group action idempotent transactions in dnf backend
|
||||||
|
- Add use_backend to yum module/action plugin
|
||||||
|
- Fix dnf handling of autoremove to be compatible with yum
|
||||||
|
- Enable installroot tests for yum4(dnf) integration testing, dnf
|
||||||
|
backend now supports that
|
||||||
|
- Switch from zip to bc for certain package install/remove test
|
||||||
|
cases in yum integration tests. The dnf depsolver downgrades
|
||||||
|
python when you uninstall zip which alters the test environment
|
||||||
|
and we have no control over that.
|
|
@ -159,8 +159,14 @@ options:
|
||||||
version_added: "2.7"
|
version_added: "2.7"
|
||||||
allow_downgrade:
|
allow_downgrade:
|
||||||
description:
|
description:
|
||||||
- This is effectively a no-op in DNF as it is the default behavior of dnf, but is an accepted parameter for feature
|
- Specify if the named package and version is allowed to downgrade
|
||||||
parity/compatibility with the I(yum) module.
|
a maybe already installed higher version of that package.
|
||||||
|
Note that setting allow_downgrade=True can make this module
|
||||||
|
behave in a non-idempotent way. The task could end up with a set
|
||||||
|
of packages that does not match the complete list of specified
|
||||||
|
packages to install (because dependencies between the downgraded
|
||||||
|
package and others can cause changes to the packages which were
|
||||||
|
in the earlier transaction).
|
||||||
type: bool
|
type: bool
|
||||||
default: False
|
default: False
|
||||||
version_added: "2.7"
|
version_added: "2.7"
|
||||||
|
@ -240,6 +246,7 @@ EXAMPLES = '''
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -255,7 +262,7 @@ except ImportError:
|
||||||
|
|
||||||
from ansible.module_utils._text import to_native, to_text
|
from ansible.module_utils._text import to_native, to_text
|
||||||
from ansible.module_utils.urls import fetch_url
|
from ansible.module_utils.urls import fetch_url
|
||||||
from ansible.module_utils.six import PY2
|
from ansible.module_utils.six import PY2, text_type
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
@ -276,6 +283,115 @@ class DnfModule(YumDnf):
|
||||||
|
|
||||||
self._ensure_dnf()
|
self._ensure_dnf()
|
||||||
|
|
||||||
|
def _package_dict(self, package):
|
||||||
|
"""Return a dictionary of information for the package."""
|
||||||
|
# NOTE: This no longer contains the 'dnfstate' field because it is
|
||||||
|
# already known based on the query type.
|
||||||
|
result = {
|
||||||
|
'name': package.name,
|
||||||
|
'arch': package.arch,
|
||||||
|
'epoch': str(package.epoch),
|
||||||
|
'release': package.release,
|
||||||
|
'version': package.version,
|
||||||
|
'repo': package.repoid}
|
||||||
|
result['nevra'] = '{epoch}:{name}-{version}-{release}.{arch}'.format(
|
||||||
|
**result)
|
||||||
|
|
||||||
|
# Added for YUM3/YUM4 compat
|
||||||
|
if package.repoid == 'installed':
|
||||||
|
result['yumstate'] = 'installed'
|
||||||
|
else:
|
||||||
|
result['yumstate'] = 'available'
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
]
|
||||||
|
|
||||||
|
rpm_arch_re = re.compile(r'(.*)\.(.*)')
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
# print '%s, %s, %s vs %s, %s, %s' % (e1, v1, r1, e2, v2, r2)
|
||||||
|
rc = dnf.rpm.rpm.labelCompare((e1, v1, r1), (e2, v2, r2))
|
||||||
|
# print '%s, %s, %s vs %s, %s, %s = %s' % (e1, v1, r1, e2, v2, r2, rc)
|
||||||
|
return rc
|
||||||
|
|
||||||
def fetch_rpm_from_url(self, spec):
|
def fetch_rpm_from_url(self, spec):
|
||||||
# FIXME: Remove this once this PR is merged:
|
# FIXME: Remove this once this PR is merged:
|
||||||
# https://github.com/ansible/ansible/pull/19172
|
# https://github.com/ansible/ansible/pull/19172
|
||||||
|
@ -287,14 +403,20 @@ class DnfModule(YumDnf):
|
||||||
try:
|
try:
|
||||||
rsp, info = fetch_url(self.module, spec)
|
rsp, info = fetch_url(self.module, spec)
|
||||||
if not rsp:
|
if not rsp:
|
||||||
self.module.fail_json(msg="Failure downloading %s, %s" % (spec, info['msg']))
|
self.module.fail_json(
|
||||||
|
msg="Failure downloading %s, %s" % (spec, info['msg']),
|
||||||
|
results=[],
|
||||||
|
)
|
||||||
data = rsp.read(BUFSIZE)
|
data = rsp.read(BUFSIZE)
|
||||||
while data:
|
while data:
|
||||||
package_file.write(data)
|
package_file.write(data)
|
||||||
data = rsp.read(BUFSIZE)
|
data = rsp.read(BUFSIZE)
|
||||||
package_file.close()
|
package_file.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.module.fail_json(msg="Failure downloading %s, %s" % (spec, to_native(e)))
|
self.module.fail_json(
|
||||||
|
msg="Failure downloading %s, %s" % (spec, to_native(e)),
|
||||||
|
results=[],
|
||||||
|
)
|
||||||
|
|
||||||
return package_file.name
|
return package_file.name
|
||||||
|
|
||||||
|
@ -308,7 +430,8 @@ class DnfModule(YumDnf):
|
||||||
if self.module.check_mode:
|
if self.module.check_mode:
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
msg="`{0}` is not installed, but it is required"
|
msg="`{0}` is not installed, but it is required"
|
||||||
"for the Ansible dnf module.".format(package)
|
"for the Ansible dnf module.".format(package),
|
||||||
|
results=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.module.run_command(['dnf', 'install', '-y', package], check_rc=True)
|
self.module.run_command(['dnf', 'install', '-y', package], check_rc=True)
|
||||||
|
@ -323,7 +446,8 @@ class DnfModule(YumDnf):
|
||||||
except ImportError:
|
except ImportError:
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
msg="Could not import the dnf python module. "
|
msg="Could not import the dnf python module. "
|
||||||
"Please install `{0}` package.".format(package)
|
"Please install `{0}` package.".format(package),
|
||||||
|
results=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
def _configure_base(self, base, conf_file, disable_gpg_check, installroot='/'):
|
def _configure_base(self, base, conf_file, disable_gpg_check, installroot='/'):
|
||||||
|
@ -356,7 +480,7 @@ class DnfModule(YumDnf):
|
||||||
|
|
||||||
# Set disable_excludes
|
# Set disable_excludes
|
||||||
if self.disable_excludes:
|
if self.disable_excludes:
|
||||||
conf.disable_excludes = [self.disable_excludes]
|
conf.disable_excludes.append(self.disable_excludes)
|
||||||
|
|
||||||
# Set releasever
|
# Set releasever
|
||||||
if self.releasever is not None:
|
if self.releasever is not None:
|
||||||
|
@ -374,10 +498,15 @@ class DnfModule(YumDnf):
|
||||||
# Fail if we can't read the configuration file.
|
# Fail if we can't read the configuration file.
|
||||||
if not os.access(conf_file, os.R_OK):
|
if not os.access(conf_file, os.R_OK):
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
msg="cannot read configuration file", conf_file=conf_file)
|
msg="cannot read configuration file", conf_file=conf_file,
|
||||||
|
results=[],
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
conf.config_file_path = conf_file
|
conf.config_file_path = conf_file
|
||||||
|
|
||||||
|
# Default in dnf upstream is true
|
||||||
|
conf.clean_requirements_on_remove = self.autoremove
|
||||||
|
|
||||||
# Read the configuration file
|
# Read the configuration file
|
||||||
conf.read()
|
conf.read()
|
||||||
|
|
||||||
|
@ -412,22 +541,6 @@ class DnfModule(YumDnf):
|
||||||
base.update_cache()
|
base.update_cache()
|
||||||
return base
|
return base
|
||||||
|
|
||||||
def _package_dict(self, package):
|
|
||||||
"""Return a dictionary of information for the package."""
|
|
||||||
# NOTE: This no longer contains the 'dnfstate' field because it is
|
|
||||||
# already known based on the query type.
|
|
||||||
result = {
|
|
||||||
'name': package.name,
|
|
||||||
'arch': package.arch,
|
|
||||||
'epoch': str(package.epoch),
|
|
||||||
'release': package.release,
|
|
||||||
'version': package.version,
|
|
||||||
'repo': package.repoid}
|
|
||||||
result['nevra'] = '{epoch}:{name}-{version}-{release}.{arch}'.format(
|
|
||||||
**result)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def list_items(self, command):
|
def list_items(self, command):
|
||||||
"""List package info based on the command."""
|
"""List package info based on the command."""
|
||||||
# Rename updates to upgrades
|
# Rename updates to upgrades
|
||||||
|
@ -449,14 +562,138 @@ class DnfModule(YumDnf):
|
||||||
packages = dnf.subject.Subject(command).get_best_query(self.base.sack)
|
packages = dnf.subject.Subject(command).get_best_query(self.base.sack)
|
||||||
results = [self._package_dict(package) for package in packages]
|
results = [self._package_dict(package) for package in packages]
|
||||||
|
|
||||||
self.module.exit_json(results=results)
|
self.module.exit_json(msg="", results=results)
|
||||||
|
|
||||||
def _mark_package_install(self, pkg_spec):
|
def _is_installed(self, pkg):
|
||||||
"""Mark the package for install."""
|
installed = self.base.sack.query().installed()
|
||||||
|
if installed.filter(name=pkg):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _is_group_installed(self, group):
|
||||||
|
"""
|
||||||
|
Check if a group is installed (the sum of the package set that makes up a group)
|
||||||
|
|
||||||
|
This is necessary until the upstream dnf API bug is fixed where installing
|
||||||
|
a group via the dnf API doesn't actually mark the group as installed
|
||||||
|
https://bugzilla.redhat.com/show_bug.cgi?id=1620324
|
||||||
|
"""
|
||||||
|
pkg_set = []
|
||||||
|
dnf_group = self.base.comps.group_by_pattern(group)
|
||||||
try:
|
try:
|
||||||
self.base.install(pkg_spec)
|
if dnf_group:
|
||||||
except dnf.exceptions.MarkingError:
|
for pkg_type in dnf.const.GROUP_PACKAGE_TYPES:
|
||||||
self.module.fail_json(msg="No package {0} available.".format(pkg_spec))
|
for pkg in getattr(dnf_group, '{0}_packages'.format(pkg_type)):
|
||||||
|
pkg_set.append(pkg.name)
|
||||||
|
except AttributeError as e:
|
||||||
|
self.module.fail_json(
|
||||||
|
msg="Error attempting to determine package group installed status: {0}".format(group),
|
||||||
|
results=[],
|
||||||
|
rc=1,
|
||||||
|
failures=[to_native(e), ],
|
||||||
|
)
|
||||||
|
|
||||||
|
for pkg in pkg_set:
|
||||||
|
if not self._is_installed(pkg):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _is_newer_version_installed(self, pkg_name):
|
||||||
|
candidate_pkg = self._packagename_dict(pkg_name)
|
||||||
|
if not candidate_pkg:
|
||||||
|
# The user didn't provide a versioned rpm, so version checking is
|
||||||
|
# not required
|
||||||
|
return False
|
||||||
|
|
||||||
|
installed = self.base.sack.query().installed()
|
||||||
|
installed_pkg = installed.filter(name=candidate_pkg['name']).run()
|
||||||
|
if installed_pkg:
|
||||||
|
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'],
|
||||||
|
)
|
||||||
|
|
||||||
|
if evr_cmp == 1:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _mark_package_install(self, pkg_spec, upgrade=False):
|
||||||
|
"""Mark the package for install."""
|
||||||
|
is_newer_version_installed = self._is_newer_version_installed(pkg_spec)
|
||||||
|
is_installed = self._is_installed(pkg_spec)
|
||||||
|
try:
|
||||||
|
if self.allow_downgrade:
|
||||||
|
# dnf only does allow_downgrade, we have to handle this ourselves
|
||||||
|
# because it allows a possibility for non-idempotent transactions
|
||||||
|
# on a system's package set (pending the yum repo has many old
|
||||||
|
# NVRs indexed)
|
||||||
|
if upgrade:
|
||||||
|
if is_installed:
|
||||||
|
self.base.upgrade(pkg_spec)
|
||||||
|
else:
|
||||||
|
self.base.install(pkg_spec)
|
||||||
|
else:
|
||||||
|
self.base.install(pkg_spec)
|
||||||
|
elif not self.allow_downgrade and is_newer_version_installed:
|
||||||
|
return {'failed': False, 'msg': '', 'failure': '', 'rc': 0}
|
||||||
|
elif not is_newer_version_installed:
|
||||||
|
if upgrade:
|
||||||
|
if is_installed:
|
||||||
|
self.base.upgrade(pkg_spec)
|
||||||
|
else:
|
||||||
|
self.base.install(pkg_spec)
|
||||||
|
else:
|
||||||
|
self.base.install(pkg_spec)
|
||||||
|
else:
|
||||||
|
if upgrade:
|
||||||
|
if is_installed:
|
||||||
|
self.base.upgrade(pkg_spec)
|
||||||
|
else:
|
||||||
|
self.base.install(pkg_spec)
|
||||||
|
else:
|
||||||
|
self.base.install(pkg_spec)
|
||||||
|
|
||||||
|
return {'failed': False, 'msg': 'Installed: {0}'.format(pkg_spec), 'failure': '', 'rc': 0}
|
||||||
|
|
||||||
|
except dnf.exceptions.MarkingError as e:
|
||||||
|
return {
|
||||||
|
'failed': True,
|
||||||
|
'msg': "No package {0} available.".format(pkg_spec),
|
||||||
|
'failure': " ".join((pkg_spec, to_native(e))),
|
||||||
|
'rc': 1,
|
||||||
|
"results": []
|
||||||
|
}
|
||||||
|
|
||||||
|
except dnf.exceptions.DepsolveError as e:
|
||||||
|
return {
|
||||||
|
'failed': True,
|
||||||
|
'msg': "Depsolve Error occured for package {0}.".format(pkg_spec),
|
||||||
|
'failure': " ".join((pkg_spec, to_native(e))),
|
||||||
|
'rc': 1,
|
||||||
|
"results": []
|
||||||
|
}
|
||||||
|
|
||||||
|
except dnf.exceptions.Error as e:
|
||||||
|
if to_text("already installed") in to_text(e):
|
||||||
|
return {'failed': False, 'msg': '', 'failure': ''}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
'failed': True,
|
||||||
|
'msg': "Unknown Error occured for package {0}.".format(pkg_spec),
|
||||||
|
'failure': " ".join((pkg_spec, to_native(e))),
|
||||||
|
'rc': 1,
|
||||||
|
"results": []
|
||||||
|
}
|
||||||
|
|
||||||
def _parse_spec_group_file(self):
|
def _parse_spec_group_file(self):
|
||||||
pkg_specs, grp_specs, filenames = [], [], []
|
pkg_specs, grp_specs, filenames = [], [], []
|
||||||
|
@ -472,29 +709,75 @@ class DnfModule(YumDnf):
|
||||||
return pkg_specs, grp_specs, filenames
|
return pkg_specs, grp_specs, filenames
|
||||||
|
|
||||||
def _update_only(self, pkgs):
|
def _update_only(self, pkgs):
|
||||||
installed = self.base.sack.query().installed()
|
not_installed = []
|
||||||
for pkg in pkgs:
|
for pkg in pkgs:
|
||||||
if installed.filter(name=pkg):
|
if self._is_installed(pkg):
|
||||||
self.base.package_upgrade(pkg)
|
try:
|
||||||
|
if isinstance(to_text(pkg), text_type):
|
||||||
|
self.base.upgrade(pkg)
|
||||||
|
else:
|
||||||
|
self.base.package_upgrade(pkg)
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(
|
||||||
|
msg="Error occured attempting update_only operation: {0}".format(to_native(e)),
|
||||||
|
results=[],
|
||||||
|
rc=1,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
not_installed.append(pkg)
|
||||||
|
|
||||||
|
return not_installed
|
||||||
|
|
||||||
def _install_remote_rpms(self, filenames):
|
def _install_remote_rpms(self, filenames):
|
||||||
if int(dnf.__version__.split(".")[0]) >= 2:
|
if int(dnf.__version__.split(".")[0]) >= 2:
|
||||||
pkgs = list(sorted(self.base.add_remote_rpms(list(filenames)), reverse=True))
|
pkgs = list(sorted(self.base.add_remote_rpms(list(filenames)), reverse=True))
|
||||||
else:
|
else:
|
||||||
pkgs = []
|
pkgs = []
|
||||||
for filename in filenames:
|
try:
|
||||||
pkgs.append(self.base.add_remote_rpm(filename))
|
for filename in filenames:
|
||||||
|
pkgs.append(self.base.add_remote_rpm(filename))
|
||||||
|
except IOError as e:
|
||||||
|
if to_text("Can not load RPM file") in to_text(e):
|
||||||
|
self.module.fail_json(
|
||||||
|
msg="Error occured attempting remote rpm install of package: {0}. {1}".format(filename, to_native(e)),
|
||||||
|
results=[],
|
||||||
|
rc=1,
|
||||||
|
)
|
||||||
if self.update_only:
|
if self.update_only:
|
||||||
self._update_only(pkgs)
|
self._update_only(pkgs)
|
||||||
else:
|
else:
|
||||||
for pkg in pkgs:
|
for pkg in pkgs:
|
||||||
self.base.package_install(pkg)
|
try:
|
||||||
|
if self._is_newer_version_installed(self._package_dict(pkg)['nevra']):
|
||||||
|
if self.allow_downgrade:
|
||||||
|
self.base.package_install(pkg)
|
||||||
|
else:
|
||||||
|
self.base.package_install(pkg)
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(
|
||||||
|
msg="Error occured attempting remote rpm operation: {0}".format(to_native(e)),
|
||||||
|
results=[],
|
||||||
|
rc=1,
|
||||||
|
)
|
||||||
|
|
||||||
def ensure(self):
|
def ensure(self):
|
||||||
|
allow_erasing = False
|
||||||
|
|
||||||
|
response = {
|
||||||
|
'msg': "",
|
||||||
|
'changed': False,
|
||||||
|
'results': [],
|
||||||
|
'rc': 0
|
||||||
|
}
|
||||||
|
|
||||||
# Accumulate failures. Package management modules install what they can
|
# Accumulate failures. Package management modules install what they can
|
||||||
# and fail with a message about what they can't.
|
# and fail with a message about what they can't.
|
||||||
failures = []
|
failure_response = {
|
||||||
allow_erasing = False
|
'msg': "",
|
||||||
|
'failures': [],
|
||||||
|
'results': [],
|
||||||
|
'rc': 1
|
||||||
|
}
|
||||||
|
|
||||||
# Autoremove is called alone
|
# Autoremove is called alone
|
||||||
# Jump to remove path where base.autoremove() is run
|
# Jump to remove path where base.autoremove() is run
|
||||||
|
@ -503,7 +786,11 @@ class DnfModule(YumDnf):
|
||||||
self.state = 'absent'
|
self.state = 'absent'
|
||||||
|
|
||||||
if self.names == ['*'] and self.state == 'latest':
|
if self.names == ['*'] and self.state == 'latest':
|
||||||
self.base.upgrade_all()
|
try:
|
||||||
|
self.base.upgrade_all()
|
||||||
|
except dnf.exceptions.DepsolveError as e:
|
||||||
|
failure_response['msg'] = "Depsolve Error occured attempting to upgrade all packages"
|
||||||
|
self.module.fail_json(**failure_response)
|
||||||
else:
|
else:
|
||||||
pkg_specs, group_specs, filenames = self._parse_spec_group_file()
|
pkg_specs, group_specs, filenames = self._parse_spec_group_file()
|
||||||
if group_specs:
|
if group_specs:
|
||||||
|
@ -523,48 +810,72 @@ class DnfModule(YumDnf):
|
||||||
environments.append(environment.id)
|
environments.append(environment.id)
|
||||||
else:
|
else:
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
msg="No group {0} available.".format(group_spec))
|
msg="No group {0} available.".format(group_spec),
|
||||||
|
results=[],
|
||||||
|
)
|
||||||
|
|
||||||
if self.state in ['installed', 'present']:
|
if self.state in ['installed', 'present']:
|
||||||
# Install files.
|
# Install files.
|
||||||
self._install_remote_rpms(filenames)
|
self._install_remote_rpms(filenames)
|
||||||
|
for filename in filenames:
|
||||||
|
response['results'].append("Installed {0}".format(filename))
|
||||||
|
|
||||||
# Install groups.
|
# Install groups.
|
||||||
for group in groups:
|
for group in groups:
|
||||||
try:
|
try:
|
||||||
self.base.group_install(group, dnf.const.GROUP_PACKAGE_TYPES)
|
if self._is_group_installed(group):
|
||||||
|
response['results'].append("Group {0} already installed.".format(group))
|
||||||
|
else:
|
||||||
|
self.base.group_install(group, dnf.const.GROUP_PACKAGE_TYPES)
|
||||||
|
response['results'].append("Group {0} installed.".format(group))
|
||||||
|
except dnf.exceptions.DepsolveError as e:
|
||||||
|
failure_response['msg'] = "Depsolve Error occured attempting to install group: {0}".format(group)
|
||||||
|
self.module.fail_json(**failure_response)
|
||||||
except dnf.exceptions.Error as e:
|
except dnf.exceptions.Error as e:
|
||||||
# In dnf 2.0 if all the mandatory packages in a group do
|
# In dnf 2.0 if all the mandatory packages in a group do
|
||||||
# not install, an error is raised. We want to capture
|
# not install, an error is raised. We want to capture
|
||||||
# this but still install as much as possible.
|
# this but still install as much as possible.
|
||||||
failures.append((group, to_native(e)))
|
failure_response['failures'].append(" ".join((group, to_native(e))))
|
||||||
|
|
||||||
for environment in environments:
|
for environment in environments:
|
||||||
try:
|
try:
|
||||||
self.base.environment_install(environment, dnf.const.GROUP_PACKAGE_TYPES)
|
self.base.environment_install(environment, dnf.const.GROUP_PACKAGE_TYPES)
|
||||||
|
except dnf.exceptions.DepsolveError as e:
|
||||||
|
failure_response['msg'] = "Depsolve Error occured attempting to install environment: {0}".format(environment)
|
||||||
|
self.module.fail_json(**failure_response)
|
||||||
except dnf.exceptions.Error as e:
|
except dnf.exceptions.Error as e:
|
||||||
failures.append((environment, to_native(e)))
|
failure_response['failures'].append(" ".join((environment, to_native(e))))
|
||||||
|
|
||||||
# Install packages.
|
# Install packages.
|
||||||
if self.update_only:
|
if self.update_only:
|
||||||
self._update_only(pkg_specs)
|
not_installed = self._update_only(pkg_specs)
|
||||||
|
for spec in not_installed:
|
||||||
|
response['results'].append("Packages providing %s not installed due to update_only specified" % spec)
|
||||||
else:
|
else:
|
||||||
for pkg_spec in pkg_specs:
|
for pkg_spec in pkg_specs:
|
||||||
self._mark_package_install(pkg_spec)
|
install_result = self._mark_package_install(pkg_spec)
|
||||||
|
if install_result['failed']:
|
||||||
|
failure_response['msg'] += install_result['msg']
|
||||||
|
failure_response['failures'].append(install_result['failure'])
|
||||||
|
|
||||||
elif self.state == 'latest':
|
elif self.state == 'latest':
|
||||||
# "latest" is same as "installed" for filenames.
|
# "latest" is same as "installed" for filenames.
|
||||||
self._install_remote_rpms(filenames)
|
self._install_remote_rpms(filenames)
|
||||||
|
for filename in filenames:
|
||||||
|
response['results'].append("Installed {0}".format(filename))
|
||||||
|
|
||||||
for group in groups:
|
for group in groups:
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
self.base.group_upgrade(group)
|
self.base.group_upgrade(group)
|
||||||
|
response['results'].append("Group {0} upgraded.".format(group))
|
||||||
except dnf.exceptions.CompsError:
|
except dnf.exceptions.CompsError:
|
||||||
# If not already installed, try to install.
|
if not self.update_only:
|
||||||
self.base.group_install(group, dnf.const.GROUP_PACKAGE_TYPES)
|
# If not already installed, try to install.
|
||||||
|
self.base.group_install(group, dnf.const.GROUP_PACKAGE_TYPES)
|
||||||
|
response['results'].append("Group {0} installed.".format(group))
|
||||||
except dnf.exceptions.Error as e:
|
except dnf.exceptions.Error as e:
|
||||||
failures.append((group, to_native(e)))
|
failure_response['failures'].append(" ".join((group, to_native(e))))
|
||||||
|
|
||||||
for environment in environments:
|
for environment in environments:
|
||||||
try:
|
try:
|
||||||
|
@ -573,29 +884,32 @@ class DnfModule(YumDnf):
|
||||||
except dnf.exceptions.CompsError:
|
except dnf.exceptions.CompsError:
|
||||||
# If not already installed, try to install.
|
# If not already installed, try to install.
|
||||||
self.base.environment_install(environment, dnf.const.GROUP_PACKAGE_TYPES)
|
self.base.environment_install(environment, dnf.const.GROUP_PACKAGE_TYPES)
|
||||||
|
except dnf.exceptions.DepsolveError as e:
|
||||||
|
failure_response['msg'] = "Depsolve Error occured attempting to install environment: {0}".format(environment)
|
||||||
except dnf.exceptions.Error as e:
|
except dnf.exceptions.Error as e:
|
||||||
failures.append((environment, to_native(e)))
|
failure_response['failures'].append(" ".join((environment, to_native(e))))
|
||||||
|
|
||||||
if self.update_only:
|
if self.update_only:
|
||||||
self._update_only(pkg_specs)
|
not_installed = self._update_only(pkg_specs)
|
||||||
|
for spec in not_installed:
|
||||||
|
response['results'].append("Packages providing %s not installed due to update_only specified" % spec)
|
||||||
else:
|
else:
|
||||||
for pkg_spec in pkg_specs:
|
for pkg_spec in pkg_specs:
|
||||||
# best effort causes to install the latest package
|
# best effort causes to install the latest package
|
||||||
# even if not previously installed
|
# even if not previously installed
|
||||||
self.base.conf.best = True
|
self.base.conf.best = True
|
||||||
try:
|
install_result = self._mark_package_install(pkg_spec, upgrade=True)
|
||||||
self.base.install(pkg_spec)
|
if install_result['failed']:
|
||||||
except dnf.exceptions.MarkingError as e:
|
failure_response['msg'] += install_result['msg']
|
||||||
failures.append((pkg_spec, to_native(e)))
|
failure_response['failures'].append(install_result['failure'])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# state == absent
|
# state == absent
|
||||||
if self.autoremove:
|
|
||||||
self.base.conf.clean_requirements_on_remove = self.autoremove
|
|
||||||
|
|
||||||
if filenames:
|
if filenames:
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
msg="Cannot remove paths -- please specify package name.")
|
msg="Cannot remove paths -- please specify package name.",
|
||||||
|
results=[],
|
||||||
|
)
|
||||||
|
|
||||||
for group in groups:
|
for group in groups:
|
||||||
try:
|
try:
|
||||||
|
@ -604,6 +918,24 @@ class DnfModule(YumDnf):
|
||||||
# Group is already uninstalled.
|
# Group is already uninstalled.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# This is necessary until the upstream dnf API bug is fixed where installing
|
||||||
|
# a group via the dnf API doesn't actually mark the group as installed
|
||||||
|
# https://bugzilla.redhat.com/show_bug.cgi?id=1620324
|
||||||
|
if self._is_group_installed(group):
|
||||||
|
dnf_group = self.base.comps.group_by_pattern(group)
|
||||||
|
try:
|
||||||
|
if dnf_group:
|
||||||
|
for pkg_type in dnf.const.GROUP_PACKAGE_TYPES:
|
||||||
|
for pkg_spec in getattr(dnf_group, '{0}_packages'.format(pkg_type)):
|
||||||
|
self.base.remove(pkg_spec.name)
|
||||||
|
except AttributeError as e:
|
||||||
|
self.module.fail_json(
|
||||||
|
msg="Error attempting to determine package group installed status: {0}".format(group),
|
||||||
|
results=[],
|
||||||
|
rc=1,
|
||||||
|
failures=[to_native(e), ],
|
||||||
|
)
|
||||||
|
|
||||||
for environment in environments:
|
for environment in environments:
|
||||||
try:
|
try:
|
||||||
self.base.environment_remove(environment)
|
self.base.environment_remove(environment)
|
||||||
|
@ -623,45 +955,57 @@ class DnfModule(YumDnf):
|
||||||
if self.autoremove:
|
if self.autoremove:
|
||||||
self.base.autoremove()
|
self.base.autoremove()
|
||||||
|
|
||||||
if not self.base.resolve(allow_erasing=allow_erasing):
|
try:
|
||||||
if failures:
|
if not self.base.resolve(allow_erasing=allow_erasing):
|
||||||
self.module.fail_json(
|
if failure_response['failures']:
|
||||||
msg='Failed to install some of the specified packages',
|
failure_response['msg'] = 'Failed to install some of the specified packages'
|
||||||
failures=failures
|
self.module.fail_json(**failure_response)
|
||||||
)
|
|
||||||
self.module.exit_json(msg="Nothing to do")
|
|
||||||
else:
|
|
||||||
if self.module.check_mode:
|
|
||||||
if failures:
|
|
||||||
self.module.fail_json(
|
|
||||||
msg='Failed to install some of the specified packages',
|
|
||||||
failures=failures
|
|
||||||
)
|
|
||||||
self.module.exit_json(changed=True)
|
|
||||||
|
|
||||||
try:
|
response['msg'] = "Nothing to do"
|
||||||
self.base.download_packages(self.base.transaction.install_set)
|
|
||||||
except dnf.exceptions.DownloadError as e:
|
|
||||||
self.module.fail_json(msg="Failed to download packages: {0}".format(to_text(e)))
|
|
||||||
|
|
||||||
response = {'changed': True, 'results': []}
|
|
||||||
if self.download_only:
|
|
||||||
for package in self.base.transaction.install_set:
|
|
||||||
response['results'].append("Downloaded: {0}".format(package))
|
|
||||||
self.module.exit_json(**response)
|
self.module.exit_json(**response)
|
||||||
else:
|
else:
|
||||||
self.base.do_transaction()
|
response['changed'] = True
|
||||||
for package in self.base.transaction.install_set:
|
if self.module.check_mode:
|
||||||
response['results'].append("Installed: {0}".format(package))
|
if failure_response['failures']:
|
||||||
for package in self.base.transaction.remove_set:
|
failure_response['msg'] = 'Failed to install some of the specified packages',
|
||||||
response['results'].append("Removed: {0}".format(package))
|
self.module.fail_json(**failure_response)
|
||||||
|
response['msg'] = "Check mode: No changes made, but would have if not in check mode"
|
||||||
|
self.module.exit_json(**response)
|
||||||
|
|
||||||
if failures:
|
try:
|
||||||
self.module.fail_json(
|
self.base.download_packages(self.base.transaction.install_set)
|
||||||
msg='Failed to install some of the specified packages',
|
except dnf.exceptions.DownloadError as e:
|
||||||
failures=failures
|
self.module.fail_json(
|
||||||
)
|
msg="Failed to download packages: {0}".format(to_text(e)),
|
||||||
self.module.exit_json(**response)
|
results=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.download_only:
|
||||||
|
for package in self.base.transaction.install_set:
|
||||||
|
response['results'].append("Downloaded: {0}".format(package))
|
||||||
|
self.module.exit_json(**response)
|
||||||
|
else:
|
||||||
|
self.base.do_transaction()
|
||||||
|
for package in self.base.transaction.install_set:
|
||||||
|
response['results'].append("Installed: {0}".format(package))
|
||||||
|
for package in self.base.transaction.remove_set:
|
||||||
|
response['results'].append("Removed: {0}".format(package))
|
||||||
|
|
||||||
|
if failure_response['failures']:
|
||||||
|
failure_response['msg'] = 'Failed to install some of the specified packages',
|
||||||
|
self.module.exit_json(**response)
|
||||||
|
self.module.exit_json(**response)
|
||||||
|
except dnf.exceptions.DepsolveError as e:
|
||||||
|
failure_response['msg'] = "Depsolve Error occured: {0}".format(to_native(e))
|
||||||
|
self.module.fail_json(**failure_response)
|
||||||
|
except dnf.exceptions.Error as e:
|
||||||
|
if to_text("already installed") in to_text(e):
|
||||||
|
response['changed'] = False
|
||||||
|
response['results'].append("Package already installed: {0}".format(to_native(e)))
|
||||||
|
self.module.exit_json(**response)
|
||||||
|
else:
|
||||||
|
failure_response['msg'] = "Unknown Error occured: {0}".format(to_native(e))
|
||||||
|
self.module.fail_json(**failure_response)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def has_dnf():
|
def has_dnf():
|
||||||
|
@ -673,9 +1017,15 @@ class DnfModule(YumDnf):
|
||||||
# Check if autoremove is called correctly
|
# Check if autoremove is called correctly
|
||||||
if self.autoremove:
|
if self.autoremove:
|
||||||
if LooseVersion(dnf.__version__) < LooseVersion('2.0.1'):
|
if LooseVersion(dnf.__version__) < LooseVersion('2.0.1'):
|
||||||
self.module.fail_json(msg="Autoremove requires dnf>=2.0.1. Current dnf version is %s" % dnf.__version__)
|
self.module.fail_json(
|
||||||
|
msg="Autoremove requires dnf>=2.0.1. Current dnf version is %s" % dnf.__version__,
|
||||||
|
results=[],
|
||||||
|
)
|
||||||
if self.state not in ["absent", None]:
|
if self.state not in ["absent", None]:
|
||||||
self.module.fail_json(msg="Autoremove should be used alone or with state=absent")
|
self.module.fail_json(
|
||||||
|
msg="Autoremove should be used alone or with state=absent",
|
||||||
|
results=[],
|
||||||
|
)
|
||||||
|
|
||||||
# Set state as installed by default
|
# Set state as installed by default
|
||||||
# This is not set in AnsibleModule() because the following shouldn't happend
|
# This is not set in AnsibleModule() because the following shouldn't happend
|
||||||
|
@ -688,12 +1038,15 @@ class DnfModule(YumDnf):
|
||||||
self.conf_file, self.disable_gpg_check, self.disablerepo,
|
self.conf_file, self.disable_gpg_check, self.disablerepo,
|
||||||
self.enablerepo, self.installroot
|
self.enablerepo, self.installroot
|
||||||
)
|
)
|
||||||
self.list_items(self.module, self.list)
|
self.list_items(self.list)
|
||||||
else:
|
else:
|
||||||
# Note: base takes a long time to run so we want to check for failure
|
# Note: base takes a long time to run so we want to check for failure
|
||||||
# before running it.
|
# before running it.
|
||||||
if not dnf.util.am_i_root():
|
if not dnf.util.am_i_root():
|
||||||
self.module.fail_json(msg="This command has to be run under the root user.")
|
self.module.fail_json(
|
||||||
|
msg="This command has to be run under the root user.",
|
||||||
|
results=[],
|
||||||
|
)
|
||||||
self.base = self._base(
|
self.base = self._base(
|
||||||
self.conf_file, self.disable_gpg_check, self.disablerepo,
|
self.conf_file, self.disable_gpg_check, self.disablerepo,
|
||||||
self.enablerepo, self.installroot
|
self.enablerepo, self.installroot
|
||||||
|
@ -722,7 +1075,7 @@ def main():
|
||||||
try:
|
try:
|
||||||
module_implementation.run()
|
module_implementation.run()
|
||||||
except dnf.exceptions.RepoError as de:
|
except dnf.exceptions.RepoError as de:
|
||||||
module.exit_json(msg="Failed to synchronize repodata: {0}".format(de))
|
module.exit_json(msg="Failed to synchronize repodata: {0}".format(to_native(de)))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -23,6 +23,16 @@ description:
|
||||||
- Installs, upgrade, downgrades, removes, and lists packages and groups with the I(yum) package manager.
|
- Installs, upgrade, downgrades, removes, and lists packages and groups with the I(yum) package manager.
|
||||||
- This module only works on Python 2. If you require Python 3 support see the M(dnf) module.
|
- This module only works on Python 2. If you require Python 3 support see the M(dnf) module.
|
||||||
options:
|
options:
|
||||||
|
use_backend:
|
||||||
|
description:
|
||||||
|
- This module supports C(yum) (as it always has), this is known as C(yum3)/C(YUM3)/C(yum-deprecated) by
|
||||||
|
upstream yum developers. As of Ansible 2.7+, this module also supports C(YUM4), which is the
|
||||||
|
"new yum" and it has an C(dnf) backend.
|
||||||
|
- By default, this module will select the backend based on the C(ansible_pkg_mgr) fact.
|
||||||
|
required: false
|
||||||
|
default: "auto"
|
||||||
|
choices: [ auto, yum, yum4, dnf ]
|
||||||
|
version_added: "2.7"
|
||||||
name:
|
name:
|
||||||
description:
|
description:
|
||||||
- A package name or package specifier with version, like C(name-1.0).
|
- A package name or package specifier with version, like C(name-1.0).
|
||||||
|
@ -1501,6 +1511,8 @@ def main():
|
||||||
# list=repos
|
# list=repos
|
||||||
# list=pkgspec
|
# list=pkgspec
|
||||||
|
|
||||||
|
yumdnf_argument_spec['argument_spec']['use_backend'] = dict(default='auto', choices=['auto', 'yum', 'yum4', 'dnf'])
|
||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
**yumdnf_argument_spec
|
**yumdnf_argument_spec
|
||||||
)
|
)
|
||||||
|
|
100
lib/ansible/plugins/action/yum.py
Normal file
100
lib/ansible/plugins/action/yum.py
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
# (c) 2018, Ansible Project
|
||||||
|
#
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
from ansible.plugins.action import ActionBase
|
||||||
|
|
||||||
|
try:
|
||||||
|
from __main__ import display
|
||||||
|
except ImportError:
|
||||||
|
from ansible.utils.display import Display
|
||||||
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
|
class ActionModule(ActionBase):
|
||||||
|
|
||||||
|
TRANSFERS_FILES = False
|
||||||
|
|
||||||
|
def run(self, tmp=None, task_vars=None):
|
||||||
|
'''
|
||||||
|
Action plugin handler for yum3 vs yum4(dnf) operations.
|
||||||
|
|
||||||
|
Enables the yum module to use yum3 and/or yum4. Yum4 is a yum
|
||||||
|
command-line compatibility layer on top of dnf. Since the Ansible
|
||||||
|
modules for yum(aka yum3) and dnf(aka yum4) call each of yum3 and yum4's
|
||||||
|
python APIs natively on the backend, we need to handle this here and
|
||||||
|
pass off to the correct Ansible module to execute on the remote system.
|
||||||
|
'''
|
||||||
|
|
||||||
|
self._supports_check_mode = True
|
||||||
|
self._supports_async = True
|
||||||
|
|
||||||
|
result = super(ActionModule, self).run(tmp, task_vars)
|
||||||
|
del tmp # tmp no longer has any effect
|
||||||
|
|
||||||
|
# Carry-over concept from the package action plugin
|
||||||
|
module = self._task.args.get('use_backend', "auto")
|
||||||
|
|
||||||
|
if module == 'auto':
|
||||||
|
try:
|
||||||
|
if self._task.delegate_to: # if we delegate, we should use delegated host's facts
|
||||||
|
module = self._templar.template("{{hostvars['%s']['ansible_facts']['pkg_mgr']}}" % self._task.delegate_to)
|
||||||
|
else:
|
||||||
|
module = self._templar.template("{{ansible_facts.pkg_mgr}}")
|
||||||
|
except Exception:
|
||||||
|
pass # could not get it from template!
|
||||||
|
|
||||||
|
if module not in ["yum", "yum4", "dnf"]:
|
||||||
|
facts = self._execute_module(module_name="setup", module_args=dict(filter="ansible_pkg_mgr", gather_subset="!all"), task_vars=task_vars)
|
||||||
|
display.debug("Facts %s" % facts)
|
||||||
|
module = facts.get("ansible_facts", {}).get("ansible_pkg_mgr", "auto")
|
||||||
|
if (not self._task.delegate_to or self._task.delegate_facts) and module != 'auto':
|
||||||
|
result['ansible_facts'] = {'pkg_mgr': module}
|
||||||
|
|
||||||
|
if module != "auto":
|
||||||
|
|
||||||
|
if module == "yum4":
|
||||||
|
module = "dnf"
|
||||||
|
|
||||||
|
if module not in self._shared_loader_obj.module_loader:
|
||||||
|
result.update({'failed': True, 'msg': "Could not find a yum module backend for %s." % module})
|
||||||
|
else:
|
||||||
|
# run either the yum (yum3) or dnf (yum4) backend module
|
||||||
|
new_module_args = self._task.args.copy()
|
||||||
|
if 'use_backend' in new_module_args:
|
||||||
|
del new_module_args['use_backend']
|
||||||
|
|
||||||
|
display.vvvv("Running %s as the backend for the yum action plugin" % module)
|
||||||
|
result.update(self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async_val))
|
||||||
|
# Now fall through to cleanup
|
||||||
|
else:
|
||||||
|
result.update(
|
||||||
|
{
|
||||||
|
'failed': True,
|
||||||
|
'msg': ("Could not detect which major revision of yum is in use, which is required to determine module backend.",
|
||||||
|
"You can manually specify use_backend to tell the module whether to use the yum (yum3) or dnf (yum4) backend})"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# Now fall through to cleanup
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
if not self._task.async_val:
|
||||||
|
# remove a temporary path we created
|
||||||
|
self._remove_tmp_path(self._connection._shell.tmpdir)
|
||||||
|
|
||||||
|
return result
|
|
@ -301,6 +301,11 @@
|
||||||
- "'changed' in dnf_result"
|
- "'changed' in dnf_result"
|
||||||
- "'msg' in dnf_result"
|
- "'msg' in dnf_result"
|
||||||
|
|
||||||
|
- name: verify that bc is not installed
|
||||||
|
dnf:
|
||||||
|
name: bc
|
||||||
|
state: absent
|
||||||
|
|
||||||
- name: install the group again but also with a package that is not yet installed
|
- name: install the group again but also with a package that is not yet installed
|
||||||
dnf:
|
dnf:
|
||||||
name:
|
name:
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
dnf:
|
dnf:
|
||||||
name: "{{ repodir }}/foo-1.0-1.{{ ansible_architecture }}.rpm"
|
name: "{{ repodir }}/foo-1.0-1.{{ ansible_architecture }}.rpm"
|
||||||
state: present
|
state: present
|
||||||
|
allow_downgrade: True
|
||||||
register: dnf_result
|
register: dnf_result
|
||||||
|
|
||||||
- name: Check foo with rpm
|
- name: Check foo with rpm
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
mode: 0755
|
mode: 0755
|
||||||
|
|
||||||
- name: Create RPMs and put them into a repo
|
- name: Create RPMs and put them into a repo
|
||||||
shell: "python /tmp/create-repo.py {{ ansible_architecture }}"
|
shell: "{{ansible_python_interpreter}} /tmp/create-repo.py {{ ansible_architecture }}"
|
||||||
register: repo
|
register: repo
|
||||||
|
|
||||||
- set_fact:
|
- set_fact:
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
gpgcheck: no
|
gpgcheck: no
|
||||||
|
|
||||||
- name: Create RPMs and put them into a repo (i686)
|
- name: Create RPMs and put them into a repo (i686)
|
||||||
shell: "python /tmp/create-repo.py i686"
|
shell: "{{ansible_python_interpreter}} /tmp/create-repo.py i686"
|
||||||
register: repo_i686
|
register: repo_i686
|
||||||
|
|
||||||
- set_fact:
|
- set_fact:
|
||||||
|
@ -70,7 +70,7 @@
|
||||||
gpgcheck: no
|
gpgcheck: no
|
||||||
|
|
||||||
- name: Create RPMs and put them into a repo (ppc64)
|
- name: Create RPMs and put them into a repo (ppc64)
|
||||||
shell: "python /tmp/create-repo.py ppc64"
|
shell: "{{ansible_python_interpreter}} /tmp/create-repo.py ppc64"
|
||||||
register: repo_ppc64
|
register: repo_ppc64
|
||||||
|
|
||||||
- set_fact:
|
- set_fact:
|
||||||
|
|
|
@ -46,8 +46,8 @@
|
||||||
- ansible_distribution in ['RedHat', 'CentOS', 'ScientificLinux'] and ansible_distribution_major_version|int <= 6
|
- ansible_distribution in ['RedHat', 'CentOS', 'ScientificLinux'] and ansible_distribution_major_version|int <= 6
|
||||||
when:
|
when:
|
||||||
- ansible_distribution in ['RedHat', 'CentOS', 'ScientificLinux', 'Fedora']
|
- ansible_distribution in ['RedHat', 'CentOS', 'ScientificLinux', 'Fedora']
|
||||||
- ansible_python.version.major == 2
|
|
||||||
|
|
||||||
|
# DNF1 doesn't handle downgrade operations properly (Fedora < 26)
|
||||||
- block:
|
- block:
|
||||||
- include: 'repo.yml'
|
- include: 'repo.yml'
|
||||||
always:
|
always:
|
||||||
|
@ -58,16 +58,10 @@
|
||||||
- command: yum clean metadata
|
- command: yum clean metadata
|
||||||
when:
|
when:
|
||||||
- ansible_distribution in ['RedHat', 'CentOS', 'ScientificLinux', 'Fedora']
|
- ansible_distribution in ['RedHat', 'CentOS', 'ScientificLinux', 'Fedora']
|
||||||
- ansible_python.version.major == 2
|
|
||||||
|
|
||||||
# We can't run yum --installroot tests on dnf systems. Dnf systems revert to
|
|
||||||
# yum-deprecated, and yum-deprecated refuses to run if yum.conf exists
|
|
||||||
# so we cannot configure yum-deprecated correctly in an empty /tmp/fake.root/
|
|
||||||
# It will always run with $releasever unset
|
|
||||||
- include: 'yuminstallroot.yml'
|
- include: 'yuminstallroot.yml'
|
||||||
when:
|
when:
|
||||||
- (ansible_distribution in ['RedHat', 'CentOS', 'ScientificLinux'] or (ansible_distribution in ['Fedora'] and ansible_distribution_major_version|int < 23))
|
- ansible_distribution in ['RedHat', 'CentOS', 'ScientificLinux', 'Fedora']
|
||||||
- ansible_python.version.major == 2
|
|
||||||
|
|
||||||
# el6 has a broken yum group implementation, when you try to remove a group it goes through
|
# el6 has a broken yum group implementation, when you try to remove a group it goes through
|
||||||
# deps and ends up with trying to remove yum itself and the whole process fails
|
# deps and ends up with trying to remove yum itself and the whole process fails
|
||||||
|
@ -75,4 +69,3 @@
|
||||||
- include: 'yum_group_remove.yml'
|
- include: 'yum_group_remove.yml'
|
||||||
when:
|
when:
|
||||||
- (ansible_distribution in ['RedHat', 'CentOS', 'ScientificLinux'] and ansible_distribution_major_version|int > 6) or ansible_distribution in ['Fedora']
|
- (ansible_distribution in ['RedHat', 'CentOS', 'ScientificLinux'] and ansible_distribution_major_version|int > 6) or ansible_distribution in ['Fedora']
|
||||||
- ansible_python.version.major == 2
|
|
||||||
|
|
|
@ -73,10 +73,11 @@
|
||||||
name: foo
|
name: foo
|
||||||
state: absent
|
state: absent
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
- name: Install 1:foo-1.0-2
|
- name: Downgrade foo
|
||||||
yum:
|
yum:
|
||||||
name: "1:foo-1.0-2.{{ ansible_architecture }}"
|
name: foo-1.0-1
|
||||||
state: present
|
state: present
|
||||||
|
allow_downgrade: yes
|
||||||
register: yum_result
|
register: yum_result
|
||||||
|
|
||||||
- name: Check foo with rpm
|
- name: Check foo with rpm
|
||||||
|
@ -87,30 +88,7 @@
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- "yum_result.changed"
|
- "yum_result.changed"
|
||||||
- "rpm_result.stdout.startswith('foo-1.0-2')"
|
- "rpm_result.stdout.startswith('foo-1.0-1')"
|
||||||
|
|
||||||
- name: Verify yum module outputs
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "'msg' in yum_result"
|
|
||||||
- "'rc' in yum_result"
|
|
||||||
- "'results' in yum_result"
|
|
||||||
# ============================================================================
|
|
||||||
- name: Install foo-1.0-2 again
|
|
||||||
yum:
|
|
||||||
name: foo-1.0-2
|
|
||||||
state: present
|
|
||||||
register: yum_result
|
|
||||||
|
|
||||||
- name: Check foo with rpm
|
|
||||||
shell: rpm -q foo
|
|
||||||
register: rpm_result
|
|
||||||
|
|
||||||
- name: Verify installation
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "not yum_result.changed"
|
|
||||||
- "rpm_result.stdout.startswith('foo-1.0-2')"
|
|
||||||
|
|
||||||
- name: Verify yum module outputs
|
- name: Verify yum module outputs
|
||||||
assert:
|
assert:
|
||||||
|
@ -279,30 +257,6 @@
|
||||||
- "not yum_result.changed"
|
- "not yum_result.changed"
|
||||||
- "rpm_result.stdout.startswith('foo-1.0-2')"
|
- "rpm_result.stdout.startswith('foo-1.0-2')"
|
||||||
|
|
||||||
- name: Verify yum module outputs
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "'msg' in yum_result"
|
|
||||||
- "'rc' in yum_result"
|
|
||||||
- "'results' in yum_result"
|
|
||||||
# ============================================================================
|
|
||||||
- name: Downgrade foo
|
|
||||||
yum:
|
|
||||||
name: foo-1.0-1
|
|
||||||
state: present
|
|
||||||
allow_downgrade: yes
|
|
||||||
register: yum_result
|
|
||||||
|
|
||||||
- name: Check foo with rpm
|
|
||||||
shell: rpm -q foo
|
|
||||||
register: rpm_result
|
|
||||||
|
|
||||||
- name: Verify installation
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- "yum_result.changed"
|
|
||||||
- "rpm_result.stdout.startswith('foo-1.0-1')"
|
|
||||||
|
|
||||||
- name: Verify yum module outputs
|
- name: Verify yum module outputs
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
|
@ -506,3 +460,99 @@
|
||||||
yum:
|
yum:
|
||||||
name: foo
|
name: foo
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
|
# FIXME: dnf currently doesn't support epoch as part of it's pkg_spec for
|
||||||
|
# finding install candidates
|
||||||
|
# https://bugzilla.redhat.com/show_bug.cgi?id=1619687
|
||||||
|
- block:
|
||||||
|
- name: Install 1:foo-1.0-2
|
||||||
|
yum:
|
||||||
|
name: "1:foo-1.0-2.{{ ansible_architecture }}"
|
||||||
|
state: present
|
||||||
|
register: yum_result
|
||||||
|
|
||||||
|
- name: Check foo with rpm
|
||||||
|
shell: rpm -q foo
|
||||||
|
register: rpm_result
|
||||||
|
|
||||||
|
- name: Verify installation
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "yum_result.changed"
|
||||||
|
- "rpm_result.stdout.startswith('foo-1.0-2')"
|
||||||
|
|
||||||
|
- name: Verify yum module outputs
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "'msg' in yum_result"
|
||||||
|
- "'rc' in yum_result"
|
||||||
|
- "'results' in yum_result"
|
||||||
|
always:
|
||||||
|
- name: Clean up
|
||||||
|
yum:
|
||||||
|
name: foo
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
when: ansible_pkg_mgr == 'yum'
|
||||||
|
|
||||||
|
# DNF1 (Fedora < 26) had some issues:
|
||||||
|
# - did not accept architecture tag as valid component of a package spec unless
|
||||||
|
# installing a file (i.e. can't search the repo)
|
||||||
|
# - doesn't handle downgrade transactions via the API properly, marks it as a
|
||||||
|
# conflict
|
||||||
|
#
|
||||||
|
# NOTE: Both DNF1 and Fedora < 26 have long been EOL'd by their respective
|
||||||
|
# upstreams
|
||||||
|
- block:
|
||||||
|
# ============================================================================
|
||||||
|
- name: Install foo-1.0-2
|
||||||
|
yum:
|
||||||
|
name: "foo-1.0-2.{{ ansible_architecture }}"
|
||||||
|
state: present
|
||||||
|
register: yum_result
|
||||||
|
|
||||||
|
- name: Check foo with rpm
|
||||||
|
shell: rpm -q foo
|
||||||
|
register: rpm_result
|
||||||
|
|
||||||
|
- name: Verify installation
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "yum_result.changed"
|
||||||
|
- "rpm_result.stdout.startswith('foo-1.0-2')"
|
||||||
|
|
||||||
|
- name: Verify yum module outputs
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "'msg' in yum_result"
|
||||||
|
- "'rc' in yum_result"
|
||||||
|
- "'results' in yum_result"
|
||||||
|
|
||||||
|
- name: Install foo-1.0-2 again
|
||||||
|
yum:
|
||||||
|
name: foo-1.0-2
|
||||||
|
state: present
|
||||||
|
register: yum_result
|
||||||
|
|
||||||
|
- name: Check foo with rpm
|
||||||
|
shell: rpm -q foo
|
||||||
|
register: rpm_result
|
||||||
|
|
||||||
|
- name: Verify installation
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "not yum_result.changed"
|
||||||
|
- "rpm_result.stdout.startswith('foo-1.0-2')"
|
||||||
|
|
||||||
|
- name: Verify yum module outputs
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "'msg' in yum_result"
|
||||||
|
- "'rc' in yum_result"
|
||||||
|
- "'results' in yum_result"
|
||||||
|
always:
|
||||||
|
- name: Clean up
|
||||||
|
yum:
|
||||||
|
name: foo
|
||||||
|
state: absent
|
||||||
|
when: not (ansible_distribution == "Fedora" and ansible_distribution_major_version|int < 26)
|
||||||
|
|
|
@ -477,6 +477,11 @@
|
||||||
|
|
||||||
- set_fact:
|
- set_fact:
|
||||||
pkg_url: https://s3.amazonaws.com/ansible-ci-files/test/integration/targets/yum/fpaste-0.3.7.4.1-2.el7.noarch.rpm
|
pkg_url: https://s3.amazonaws.com/ansible-ci-files/test/integration/targets/yum/fpaste-0.3.7.4.1-2.el7.noarch.rpm
|
||||||
|
when: ansible_python.version.major == 2
|
||||||
|
|
||||||
|
- set_fact:
|
||||||
|
pkg_url: https://s3.amazonaws.com/ansible-ci-files/test/integration/targets/yum/fpaste-0.3.9.2-1.fc28.noarch.rpm
|
||||||
|
when: ansible_python.version.major == 3
|
||||||
# setup end
|
# setup end
|
||||||
|
|
||||||
- name: download an rpm
|
- name: download an rpm
|
||||||
|
@ -566,7 +571,6 @@
|
||||||
that:
|
that:
|
||||||
- "'changed' in no_nevra_info_result"
|
- "'changed' in no_nevra_info_result"
|
||||||
- "'msg' in no_nevra_info_result"
|
- "'msg' in no_nevra_info_result"
|
||||||
- "'Failed to get nevra information from RPM package' in no_nevra_info_result.msg"
|
|
||||||
|
|
||||||
- name: Delete a temp RPM file
|
- name: Delete a temp RPM file
|
||||||
file:
|
file:
|
||||||
|
@ -583,102 +587,131 @@
|
||||||
yum_version: "{%- if item.yumstate == 'installed' -%}{{ item.version }}{%- else -%}{{ yum_version }}{%- endif -%}"
|
yum_version: "{%- if item.yumstate == 'installed' -%}{{ item.version }}{%- else -%}{{ yum_version }}{%- endif -%}"
|
||||||
with_items: "{{ yum_version.results }}"
|
with_items: "{{ yum_version.results }}"
|
||||||
|
|
||||||
- name: check whether yum supports disableexcludes (>= 3.4)
|
- block:
|
||||||
set_fact:
|
- name: check whether yum supports disableexcludes (>= 3.4)
|
||||||
supports_disable_excludes: "{{ yum_version is version_compare('3.4.0', '>=') }}"
|
set_fact:
|
||||||
|
supports_disable_excludes: "{{ yum_version is version_compare('3.4.0', '>=') }}"
|
||||||
|
when: ansible_pkg_mgr == "yum"
|
||||||
|
|
||||||
- name: uninstall zip
|
- name: unset disableexcludes tests for dnf(yum4) backend temporarily
|
||||||
yum: name=zip state=removed
|
set_fact:
|
||||||
|
supports_disable_excludes: True
|
||||||
|
when: ansible_pkg_mgr == "dnf"
|
||||||
|
|
||||||
- name: check zip with rpm
|
- name: uninstall bc
|
||||||
shell: rpm -q zip
|
yum: name=bc state=removed
|
||||||
ignore_errors: True
|
|
||||||
register: rpm_zip_result
|
|
||||||
|
|
||||||
- name: verify zip is uninstalled
|
- name: check bc with rpm
|
||||||
assert:
|
shell: rpm -q bc
|
||||||
that:
|
ignore_errors: True
|
||||||
- "rpm_zip_result is failed"
|
register: rpm_bc_result
|
||||||
|
|
||||||
- name: exclude zip
|
- name: verify bc is uninstalled
|
||||||
lineinfile:
|
assert:
|
||||||
dest: /etc/yum.conf
|
that:
|
||||||
regexp: (^exclude=)(.)*
|
- "rpm_bc_result is failed"
|
||||||
line: "exclude=zip*"
|
|
||||||
state: present
|
|
||||||
|
|
||||||
# begin test case where disable_excludes is supported
|
- name: exclude bc (yum backend)
|
||||||
- name: Try install zip without disable_excludes
|
lineinfile:
|
||||||
yum: name=zip state=latest
|
dest: /etc/yum.conf
|
||||||
register: yum_zip_result
|
regexp: (^exclude=)(.)*
|
||||||
ignore_errors: True
|
line: "exclude=bc*"
|
||||||
when: supports_disable_excludes
|
state: present
|
||||||
|
when: ansible_pkg_mgr == 'yum'
|
||||||
|
|
||||||
- name: verify zip did not install because it is in exclude list
|
- name: exclude bc (dnf backend)
|
||||||
assert:
|
lineinfile:
|
||||||
that:
|
dest: /etc/dnf/dnf.conf
|
||||||
- "yum_zip_result is failed"
|
regexp: (^excludepkgs=)(.)*
|
||||||
when: supports_disable_excludes
|
line: "excludepkgs=bc*"
|
||||||
|
state: present
|
||||||
|
when: ansible_pkg_mgr == 'dnf'
|
||||||
|
|
||||||
- name: install zip with disable_excludes
|
# begin test case where disable_excludes is supported
|
||||||
yum: name=zip state=latest disable_excludes=all
|
- name: Try install bc without disable_excludes
|
||||||
register: yum_zip_result_using_excludes
|
yum: name=bc state=latest
|
||||||
when: supports_disable_excludes
|
register: yum_bc_result
|
||||||
|
ignore_errors: True
|
||||||
|
when: supports_disable_excludes
|
||||||
|
|
||||||
- name: verify zip did install using disable_excludes=all
|
- name: verify bc did not install because it is in exclude list
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- "yum_zip_result_using_excludes is success"
|
- "yum_bc_result is failed"
|
||||||
- "yum_zip_result_using_excludes is changed"
|
when: supports_disable_excludes
|
||||||
- "yum_zip_result_using_excludes is not failed"
|
|
||||||
when: supports_disable_excludes
|
|
||||||
|
|
||||||
- name: remove exclude zip (cleanup yum.conf)
|
- name: install bc with disable_excludes
|
||||||
lineinfile:
|
yum: name=bc state=latest disable_excludes=all
|
||||||
dest: /etc/yum.conf
|
register: yum_bc_result_using_excludes
|
||||||
regexp: (^exclude=zip*)
|
when: supports_disable_excludes
|
||||||
line: "exclude="
|
|
||||||
state: present
|
|
||||||
when: supports_disable_excludes
|
|
||||||
# end test case where disable_excludes is supported
|
|
||||||
|
|
||||||
# begin test case where disable_excludes is not supported
|
- name: verify bc did install using disable_excludes=all
|
||||||
- name: Try install zip with disable_excludes
|
assert:
|
||||||
yum: name=zip state=latest disable_excludes=all
|
that:
|
||||||
register: yum_fail_zip_result_old_yum
|
- "yum_bc_result_using_excludes is success"
|
||||||
ignore_errors: True
|
- "yum_bc_result_using_excludes is changed"
|
||||||
when: not supports_disable_excludes
|
- "yum_bc_result_using_excludes is not failed"
|
||||||
|
when: supports_disable_excludes
|
||||||
|
|
||||||
- name: verify packages did not install because yum version is unsupported
|
- name: remove exclude bc (cleanup yum.conf)
|
||||||
assert:
|
lineinfile:
|
||||||
that:
|
dest: /etc/yum.conf
|
||||||
- "yum_fail_zip_result_old_yum is failed"
|
regexp: (^exclude=bc*)
|
||||||
when: not supports_disable_excludes
|
line: "exclude="
|
||||||
|
state: present
|
||||||
|
when: supports_disable_excludes and (ansible_pkg_mgr == 'yum')
|
||||||
|
|
||||||
- name: verify yum module outputs
|
- name: remove exclude bc (cleanup dnf.conf)
|
||||||
assert:
|
lineinfile:
|
||||||
that:
|
dest: /etc/dnf/dnf.conf
|
||||||
- "'is available in yum version 3.4 and onwards.' in yum_fail_zip_result_old_yum.msg"
|
regexp: (^excludepkgs=bc*)
|
||||||
when: not supports_disable_excludes
|
line: "excludepkgs="
|
||||||
|
state: present
|
||||||
|
when: ansible_pkg_mgr == 'dnf'
|
||||||
|
# end test case where disable_excludes is supported
|
||||||
|
|
||||||
- name: remove exclude zip (cleanup yum.conf)
|
# begin test case where disable_excludes is not supported
|
||||||
lineinfile:
|
- name: Try install bc with disable_excludes
|
||||||
dest: /etc/yum.conf
|
yum: name=bc state=latest disable_excludes=all
|
||||||
regexp: (^exclude=zip*)
|
register: yum_fail_bc_result_old_yum
|
||||||
line: "exclude="
|
ignore_errors: True
|
||||||
state: present
|
when: not supports_disable_excludes
|
||||||
when: not supports_disable_excludes
|
|
||||||
|
|
||||||
- name: install zip (bring test env in same state as when testing started)
|
- name: verify packages did not install because yum version is unsupported
|
||||||
yum: name=zip state=latest
|
assert:
|
||||||
register: yum_zip_result_old_yum
|
that:
|
||||||
when: not supports_disable_excludes
|
- "yum_fail_bc_result_old_yum is failed"
|
||||||
|
when: not supports_disable_excludes
|
||||||
|
|
||||||
- name: verify zip installed
|
- name: verify yum module outputs
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- "yum_zip_result_old_yum is success"
|
- "'is available in yum version 3.4 and onwards.' in yum_fail_bc_result_old_yum.msg"
|
||||||
- "yum_zip_result_old_yum is changed"
|
when: not supports_disable_excludes
|
||||||
- "yum_zip_result_old_yum is not failed"
|
|
||||||
when: not supports_disable_excludes
|
- name: remove exclude bc (cleanup yum.conf)
|
||||||
# end test case where disable_excludes is not supported
|
lineinfile:
|
||||||
|
dest: /etc/yum.conf
|
||||||
|
regexp: (^exclude=bc*)
|
||||||
|
line: "exclude="
|
||||||
|
state: present
|
||||||
|
when: not supports_disable_excludes and ansible_pkg_mgr == 'yum'
|
||||||
|
|
||||||
|
- name: install bc (bring test env in same state as when testing started)
|
||||||
|
yum: name=bc state=latest
|
||||||
|
register: yum_bc_result_old_yum
|
||||||
|
when: not supports_disable_excludes
|
||||||
|
|
||||||
|
- name: verify bc installed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "yum_bc_result_old_yum is success"
|
||||||
|
- "yum_bc_result_old_yum is changed"
|
||||||
|
- "yum_bc_result_old_yum is not failed"
|
||||||
|
when: not supports_disable_excludes and ansible_pkg_mgr == "yum"
|
||||||
|
# end test case where disable_excludes is not supported
|
||||||
|
|
||||||
|
# Fedora < 26 has a bug in dnf where package excludes in dnf.conf aren't
|
||||||
|
# actually honored and those releases are EOL'd so we have no expectation they
|
||||||
|
# will ever be fixed
|
||||||
|
when: not ((ansible_distribution == "Fedora") and (ansible_distribution_major_version|int < 26))
|
|
@ -5,6 +5,16 @@
|
||||||
with_items:
|
with_items:
|
||||||
- "@Development Tools"
|
- "@Development Tools"
|
||||||
- yum-utils
|
- yum-utils
|
||||||
|
when: ansible_pkg_mgr == "yum"
|
||||||
|
|
||||||
|
- name: install a group to test and dnf-utils
|
||||||
|
yum:
|
||||||
|
name: "{{ item }}"
|
||||||
|
state: present
|
||||||
|
with_items:
|
||||||
|
- "@Development Tools"
|
||||||
|
- dnf-utils
|
||||||
|
when: ansible_pkg_mgr == "dnf"
|
||||||
|
|
||||||
- name: check mode remove the group
|
- name: check mode remove the group
|
||||||
yum:
|
yum:
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- lib_result.failed
|
- lib_result.failed
|
||||||
- "lib_result.msg=='No package libbdplus available.'"
|
- "lib_result.msg=='Failed to install some of the specified packages'"
|
||||||
|
|
||||||
- name: re-add rpmfusion
|
- name: re-add rpmfusion
|
||||||
yum_repository:
|
yum_repository:
|
||||||
|
|
Loading…
Reference in a new issue