From 6edeafa438eb560e0cef0e15dfa9a61e25e83ccb Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Wed, 16 Sep 2026 18:25:29 -0300 Subject: [PATCH] fix(xcat-core): go-xcat asks to remove packages that are not installed GO_XCAT_UNINSTALL_LIST names every package go-xcat installs plus the ones xCAT has stopped producing, so a node installed before they went away is still cleaned up. uninstall_xcat handed that whole list to the package manager, and most of it is not installed on any one node: apt answers "Unable to locate package" for a name it does not know and go-xcat warns once per package, while dnf answers "No match for argument" and fails the call when nothing matches. installed_packages() asks the local package database which of them this system has, and uninstall_xcat and uninstall_xcat_completely pass only those on. A node with none of them installed calls the package manager not at all. This is also what makes a retired name in the list free to keep. go_xcat_uninstall_installed_only.t runs uninstall_xcat with rpm and dpkg-query answering from a fake package database and remove_package shadowed, and reads the argument list the package manager would have been given. A filter that kept everything and a filter that kept nothing each fail three of its four assertions. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- xCAT-server/share/xcat/tools/go-xcat | 43 ++++++++++++++++++- .../unit/go_xcat_uninstall_installed_only.t | 5 ++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/xCAT-server/share/xcat/tools/go-xcat b/xCAT-server/share/xcat/tools/go-xcat index f1c452c7c..b63b05c0a 100755 --- a/xCAT-server/share/xcat/tools/go-xcat +++ b/xCAT-server/share/xcat/tools/go-xcat @@ -2033,10 +2033,47 @@ function update_xcat() install_packages "$@" "${install_list[@]}" } +# $@ package names +# Prints the ones this system actually has. `go-xcat uninstall` names packages xCAT no +# longer produces, so that a node installed before they went away is still cleaned up. +# Handing those names to the package manager on a node that never had them costs a +# warning each on apt, and fails the whole call on dnf when none of them match. +function installed_packages_rpm() +{ + type rpm >/dev/null 2>&1 || return 255 + local package="" + for package in "$@" + do + rpm -q --quiet "${package}" && echo "${package}" + done + return 0 +} + +# $@ package names +function installed_packages_deb() +{ + type dpkg-query >/dev/null 2>&1 || return 255 + local package="" + for package in "$@" + do + [[ "$(dpkg-query --show '--showformat=${Status}' "${package}" 2>/dev/null)" \ + = "install ok installed" ]] && echo "${package}" + done + return 0 +} + +function installed_packages() +{ + function_dispatch "${FUNCNAME[0]}" "$@" +} + # $1 -y function uninstall_xcat() { - remove_package "$@" "${GO_XCAT_UNINSTALL_LIST[@]}" + local -a present=() + readarray -t present < <(installed_packages "${GO_XCAT_UNINSTALL_LIST[@]}") + [[ "${#present[@]}" -gt "0" ]] || return 0 + remove_package "$@" "${present[@]}" } function kill_xcat() @@ -2091,7 +2128,9 @@ function trash_xcat() function uninstall_xcat_completely() { - purge_package -y "${GO_XCAT_UNINSTALL_LIST[@]}" + local -a present=() + readarray -t present < <(installed_packages "${GO_XCAT_UNINSTALL_LIST[@]}") + [[ "${#present[@]}" -eq "0" ]] || purge_package -y "${present[@]}" kill_xcat trash_xcat diff --git a/xCAT-test/unit/go_xcat_uninstall_installed_only.t b/xCAT-test/unit/go_xcat_uninstall_installed_only.t index 31464393c..168aa443d 100755 --- a/xCAT-test/unit/go_xcat_uninstall_installed_only.t +++ b/xCAT-test/unit/go_xcat_uninstall_installed_only.t @@ -79,7 +79,10 @@ sub uninstall_args { my $present = join ' ', @{$installed}; open my $fh, '>', $driver or die "$driver: $!"; print {$fh} "set -u\n"; - print {$fh} "type() { return 1; }\n"; # the rpm branch of the lists + # An rpm host: dpkg is absent, so the lists take the rpm branch and the deb arm of the + # dispatch stands down. Shadowing `type` to fail for everything would stand both arms + # down and the filter would answer nothing, whatever it does. + print {$fh} "type() { case \"\$1\" in dpkg|dpkg-query|apt-get) return 1 ;; *) return 0 ;; esac; }\n"; print {$fh} $lists, "\n"; print {$fh} <<"BASH"; INSTALLED="$present"