From ccdd3e4d9143e9006b0ef9178c160755b58ce9ed Mon Sep 17 00:00:00 2001 From: Leonid Fedotov <3584432+iLeonidze@users.noreply.github.com> Date: Wed, 24 Nov 2021 14:25:11 +0300 Subject: [PATCH] MANOPD-70560 Fix cached packages list in inventory (#65) --- kubetool/audit.py | 3 ++ kubetool/core/annotations.py | 2 +- kubetool/core/cluster.py | 62 +++++++++++++++++++++--------------- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/kubetool/audit.py b/kubetool/audit.py index a3d501687..e974fd377 100644 --- a/kubetool/audit.py +++ b/kubetool/audit.py @@ -50,6 +50,9 @@ def install(group: NodeGroup, enable_service: bool = True, force: bool = False) log.verbose('Searching for already installed auditd package...') debian_group = group.get_subgroup_with_os('debian') debian_package_name = cluster.get_package_association_str_for_group(debian_group, 'audit', 'package_name') + if isinstance(debian_package_name, list): + raise Exception(f'Audit can not be installed, because nodes already contains different package versions: ' + f'{str(debian_package_name)}') audit_installed_results = packages.detect_installed_package_version(debian_group, debian_package_name) log.verbose(audit_installed_results) diff --git a/kubetool/core/annotations.py b/kubetool/core/annotations.py index 1fdd93f0c..54279f2bc 100644 --- a/kubetool/core/annotations.py +++ b/kubetool/core/annotations.py @@ -1,7 +1,7 @@ from kubetool.core.group import NodeGroup -def restrict_multi_os_group(fn): +def restrict_multi_os_group(fn: callable): """ Method is an annotation that does not allow origin method to use different OS families in the same group. :param fn: Origin function to apply annotation validation to diff --git a/kubetool/core/cluster.py b/kubetool/core/cluster.py index 820179f2c..197f3d9a0 100755 --- a/kubetool/core/cluster.py +++ b/kubetool/core/cluster.py @@ -211,18 +211,21 @@ def get_associations_for_node(self, host: str) -> dict: node_os_family = self.get_os_family_for_node(host) return self.get_associations_for_os(node_os_family) - def get_package_association_for_node(self, host: str, package: str, association_key: str) -> str: + def get_package_association_for_node(self, host: str, package: str, association_key: str) -> str or list: """ Returns the specified association for the specified package from inventory for specific node :param host: The address of the node for which required to find the association :param package: The package name to get the association for :param association_key: Association key to get - :return: Association string value + :return: Association string or list value """ associations = self.get_associations_for_node(host) association_value = associations.get(package, {}).get(association_key) if association_value is None: raise Exception(f'Failed to get association "{association_key}" for package "{package}"') + if not isinstance(association_value, str) and not isinstance(association_value, list): + raise Exception(f'Unsupported association "{association_key}" value type for package "{package}", ' + f'got: {str(association_value)}') return association_value def get_package_association_for_group(self, group: NodeGroup, package: str, association_key: str) -> dict: @@ -239,14 +242,15 @@ def get_package_association_for_group(self, group: NodeGroup, package: str, asso results[node['connect_to']] = association_value return results - def get_package_association_str_for_group(self, group: NodeGroup, package: str, association_key: str) -> str: + def get_package_association_str_for_group(self, group: NodeGroup, + package: str, association_key: str) -> str or list: """ - Returns the specified association string for the specified package from inventory for entire NodeGroup. If - association value is different between some nodes, an exception will be thrown. + Returns the specified association string or list for the specified package from inventory for entire NodeGroup. + If association value is different between some nodes, an exception will be thrown. :param group: NodeGroup for which required to find the association :param package: The package name to get the association for :param association_key: Association key to get - :return: Association string value + :return: Association string or list value """ results = self.get_package_association_for_group(group, package, association_key) results_values = list(set(results.values())) @@ -265,40 +269,46 @@ def cache_package_versions(self): final_packages_list = [] if isinstance(associated_packages, str): packages_list.append(associated_packages) - else: + elif isinstance(associated_packages, list): packages_list = packages_list + associated_packages + else: + raise Exception('Unsupported associated packages object type') + for package in packages_list: detected_package_versions = list(detected_packages[package].keys()) for version in detected_package_versions: - if "not installed" in version: - # if not installed somewhere - just skip - final_packages_list.append(package) - continue - if len(detected_package_versions) == 1: - final_packages_list.append(detected_package_versions[0]) - else: - # if detected multiple versions, then such broken package should be skipped - final_packages_list.append(package) + # add package version to list only if it was found as installed + if "not installed" not in version: + final_packages_list.append(version) + + # if there no versions detected, then set package version to default + if not final_packages_list: + final_packages_list = [package] + # if non-multiple value, then convert to simple string + # packages can contain multiple package values, like docker package + # (it has docker-ce, docker-cli and containerd.io packages for installation) if len(final_packages_list) == 1: final_packages_list = final_packages_list[0] + else: + final_packages_list = list(set(final_packages_list)) + associated_params['package_name'] = final_packages_list # packages from direct installation section if self.inventory['services']['packages']['install']: final_packages_list = [] for package in self.inventory['services']['packages']['install']['include']: + package_versions_list = [] detected_package_versions = list(detected_packages[package].keys()) for version in detected_package_versions: - if "not installed" in version: - # if not installed somewhere - just skip - final_packages_list.append(package) - continue - if len(detected_package_versions) == 1: - final_packages_list.append(detected_package_versions[0]) - else: - # if detected multiple versions, then such broken package should be skipped - final_packages_list.append(package) - self.inventory['services']['packages']['install']['include'] = final_packages_list + if "not installed" not in version: + # add package version to list only if it was found as installed + package_versions_list.append(version) + # if there no versions detected, then set package version to default + if not package_versions_list: + package_versions_list = [package] + final_packages_list = final_packages_list + package_versions_list + self.inventory['services']['packages']['install']['include'] = list(set(final_packages_list)) return detected_packages def finish(self):