diff --git a/changelogs/fragments/yum-handle-obsoletes-check-update.yaml b/changelogs/fragments/yum-handle-obsoletes-check-update.yaml new file mode 100644 index 0000000000..dc93e5049c --- /dev/null +++ b/changelogs/fragments/yum-handle-obsoletes-check-update.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - "yum - when checking for updates, now properly include Obsoletes (both old and new) package data in the module JSON output, fixes https://github.com/ansible/ansible/issues/39978" diff --git a/lib/ansible/modules/packaging/os/yum.py b/lib/ansible/modules/packaging/os/yum.py index ecf0081170..17c9aa1c06 100644 --- a/lib/ansible/modules/packaging/os/yum.py +++ b/lib/ansible/modules/packaging/os/yum.py @@ -1075,6 +1075,7 @@ class YumModule(YumDnf): @staticmethod def parse_check_update(check_update_output): updates = {} + obsoletes = {} # remove incorrect new lines in longer columns in output from yum check-update # yum line wrapping can move the repo to the next line @@ -1099,17 +1100,24 @@ class YumModule(YumDnf): # ignore irrelevant lines # '*' in line matches lines like mirror lists: # * base: mirror.corbina.net - # len(line) != 3 could be junk or a continuation + # len(line) != 3 or 6 could be junk or a continuation + # len(line) = 6 is package obsoletes # # FIXME: what is the '.' not in line conditional for? - if '*' in line or len(line) != 3 or '.' not in line[0]: + if '*' in line or len(line) not in [3, 6] or '.' not in line[0]: continue else: - pkg, version, repo = line + pkg, version, repo = line[0], line[1], line[2] name, dist = pkg.rsplit('.', 1) updates.update({name: {'version': version, 'dist': dist, 'repo': repo}}) - return updates + + if len(line) == 6: + obsolete_pkg, obsolete_version, obsolete_repo = line[3], line[4], line[5] + obsolete_name, obsolete_dist = obsolete_pkg.rsplit('.', 1) + obsoletes.update({obsolete_name: {'version': obsolete_version, 'dist': obsolete_dist, 'repo': obsolete_repo}}) + + return updates, obsoletes def latest(self, items, repoq): @@ -1122,6 +1130,7 @@ class YumModule(YumDnf): pkgs['update'] = [] pkgs['install'] = [] updates = {} + obsoletes = {} update_all = False cmd = None @@ -1135,7 +1144,7 @@ class YumModule(YumDnf): res['results'].append('Nothing to do here, all packages are up to date') return res elif rc == 100: - updates = self.parse_check_update(out) + updates, obsoletes = self.parse_check_update(out) elif rc == 1: res['msg'] = err res['rc'] = rc @@ -1267,6 +1276,9 @@ class YumModule(YumDnf): if will_update or pkgs['install']: res['changed'] = True + if obsoletes: + res['obsoletes'] = obsoletes + return res # run commands @@ -1291,6 +1303,9 @@ class YumModule(YumDnf): if rc: res['failed'] = True + if obsoletes: + res['obsoletes'] = obsoletes + return res def ensure(self, repoq): diff --git a/test/units/modules/packaging/os/test_yum.py b/test/units/modules/packaging/os/test_yum.py index 0ac554a1ff..6aa672d21f 100644 --- a/test/units/modules/packaging/os/test_yum.py +++ b/test/units/modules/packaging/os/test_yum.py @@ -127,9 +127,19 @@ glibc.x86_64 2.17-157.el7_3.1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx unwrapped_output_rhel7_obsoletes = unwrapped_output_rhel7 + wrapped_output_rhel7_obsoletes_postfix -unwrapped_output_rhel7_expected_pkgs = ["NetworkManager-openvpn", "NetworkManager-openvpn-gnome", "cabal-install", - "cgit", "python34-libs", "python34-test", "python34-tkinter", - "python34-tools", "qgit", "rdiff-backup", "stoken-libs", "xlockmore"] +unwrapped_output_rhel7_expected_new_obsoletes_pkgs = [ + "ddashboard", "python-bugzilla", "python2-futures", "python2-pip", + "python2-pyxdg", "python2-simplejson" +] +unwrapped_output_rhel7_expected_old_obsoletes_pkgs = [ + "developerdashboard", "python-bugzilla-develdashboardfixes", + "python-futures", "python-pip", "pyxdg", "python-simplejson" +] +unwrapped_output_rhel7_expected_updated_pkgs = [ + "NetworkManager-openvpn", "NetworkManager-openvpn-gnome", "cabal-install", + "cgit", "python34-libs", "python34-test", "python34-tkinter", + "python34-tools", "qgit", "rdiff-backup", "stoken-libs", "xlockmore" +] class TestYumUpdateCheckParse(unittest.TestCase): @@ -141,34 +151,34 @@ class TestYumUpdateCheckParse(unittest.TestCase): self.assertIsInstance(result, dict) def test_empty_output(self): - res = YumModule.parse_check_update("") + res, obs = YumModule.parse_check_update("") expected_pkgs = [] self._assert_expected(expected_pkgs, res) def test_longname(self): - res = YumModule.parse_check_update(longname) + res, obs = YumModule.parse_check_update(longname) expected_pkgs = ['xxxxxxxxxxxxxxxxxxxxxxxxxx', 'glibc'] self._assert_expected(expected_pkgs, res) def test_plugin_load_error(self): - res = YumModule.parse_check_update(yum_plugin_load_error) + res, obs = YumModule.parse_check_update(yum_plugin_load_error) expected_pkgs = [] self._assert_expected(expected_pkgs, res) def test_wrapped_output_1(self): - res = YumModule.parse_check_update(wrapped_output_1) + res, obs = YumModule.parse_check_update(wrapped_output_1) expected_pkgs = ["vms-agent"] self._assert_expected(expected_pkgs, res) def test_wrapped_output_2(self): - res = YumModule.parse_check_update(wrapped_output_2) + res, obs = YumModule.parse_check_update(wrapped_output_2) expected_pkgs = ["empty-empty-empty-empty-empty-empty-empty-empty-empty-empty-empty-empty-empty-empty-empty-empty-empty-empty-empty-empty", "libtiff"] self._assert_expected(expected_pkgs, res) def test_wrapped_output_3(self): - res = YumModule.parse_check_update(wrapped_output_3) + res, obs = YumModule.parse_check_update(wrapped_output_3) expected_pkgs = ["ceph", "ceph-base", "ceph-common", "ceph-mds", "ceph-mon", "ceph-osd", "ceph-selinux", "libcephfs1", "librados2", "libradosstriper1", "librbd1", "librgw2", @@ -176,16 +186,20 @@ class TestYumUpdateCheckParse(unittest.TestCase): self._assert_expected(expected_pkgs, res) def test_wrapped_output_4(self): - res = YumModule.parse_check_update(wrapped_output_4) + res, obs = YumModule.parse_check_update(wrapped_output_4) expected_pkgs = ["ipxe-roms-qemu", "quota", "quota-nls", "rdma", "screen", "sos", "sssd-client"] self._assert_expected(expected_pkgs, res) def test_wrapped_output_rhel7(self): - res = YumModule.parse_check_update(unwrapped_output_rhel7) - self._assert_expected(unwrapped_output_rhel7_expected_pkgs, res) + res, obs = YumModule.parse_check_update(unwrapped_output_rhel7) + self._assert_expected(unwrapped_output_rhel7_expected_updated_pkgs, res) def test_wrapped_output_rhel7_obsoletes(self): - res = YumModule.parse_check_update(unwrapped_output_rhel7_obsoletes) - self._assert_expected(unwrapped_output_rhel7_expected_pkgs, res) + res, obs = YumModule.parse_check_update(unwrapped_output_rhel7_obsoletes) + self._assert_expected( + unwrapped_output_rhel7_expected_updated_pkgs + unwrapped_output_rhel7_expected_new_obsoletes_pkgs, + res + ) + self._assert_expected(unwrapped_output_rhel7_expected_old_obsoletes_pkgs, obs)