diff --git a/features/schemas/ua_security_status.json b/features/schemas/ua_security_status.json index 3e166e5c22..3cd334c6b1 100644 --- a/features/schemas/ua_security_status.json +++ b/features/schemas/ua_security_status.json @@ -93,6 +93,7 @@ "type": "string", "enum": [ "upgrade_available", + "upgrade_available_not_preferred", "pending_attach", "pending_enable", "upgrade_unavailable" diff --git a/uaclient/security_status.py b/uaclient/security_status.py index 5d61cb3f76..cf9a4d97c0 100644 --- a/uaclient/security_status.py +++ b/uaclient/security_status.py @@ -17,6 +17,7 @@ get_apt_cache_datetime, get_apt_pkg_cache, get_esm_apt_pkg_cache, + get_pkg_candidate_version, ) from uaclient.config import UAConfig from uaclient.entitlements import ESMAppsEntitlement, ESMInfraEntitlement @@ -39,6 +40,7 @@ class UpdateStatus(Enum): "Represents the availability of a security package." AVAILABLE = "upgrade_available" + AVAILABLE_NOT_PREFERRED = "upgrade_available_not_preferred" UNATTACHED = "pending_attach" NOT_ENABLED = "pending_enable" UNAVAILABLE = "upgrade_unavailable" @@ -256,20 +258,40 @@ def get_livepatch_fixed_cves() -> List[Dict[str, Any]]: return [] +@lru_cache(maxsize=None) +def _is_candidate_version(pkg: str, version: str) -> bool: + """Returns True if the version is a candidate version.""" + candidate_version = get_pkg_candidate_version(pkg, check_esm_cache=True) + # print(f"candidate_version: {candidate_version}, version: {version}") + if candidate_version: + return version == candidate_version + return False + + def create_updates_list( upgradable_versions: DefaultDict[str, List[Tuple[apt_pkg.Version, str]]], ua_info: Dict[str, Any], ) -> List[Dict[str, Any]]: updates = [] for service, version_list in upgradable_versions.items(): - status = get_update_status(service, ua_info) + current_update_status = get_update_status(service, ua_info) for version, origin in version_list: + is_candidate = _is_candidate_version( + version.parent_pkg.name, version.ver_str + ) + update_status = ( + current_update_status + if is_candidate + else UpdateStatus.AVAILABLE_NOT_PREFERRED.value + ) + # print("current_update_status: ", current_update_status, " update_status: ", update_status) + # print("\n") updates.append( { "package": version.parent_pkg.name, "version": version.ver_str, "service_name": service, - "status": status, + "status": update_status, "origin": origin, "download_size": version.size, } diff --git a/uaclient/tests/test_security_status.py b/uaclient/tests/test_security_status.py index 0e7ddc5b16..6c4c2410e0 100644 --- a/uaclient/tests/test_security_status.py +++ b/uaclient/tests/test_security_status.py @@ -508,8 +508,10 @@ def test_filter_updates_when_esm_disabled(self, m_esm_cache): ) @mock.patch(M_PATH + "filter_updates") @mock.patch(M_PATH + "get_apt_pkg_cache") + @mock.patch(M_PATH + "get_pkg_candidate_version", return_value=None) def test_security_status_dict( self, + m_pkg_candidate_version, m_cache, m_filter_sec_updates, _m_get_origin, @@ -524,9 +526,24 @@ def test_security_status_dict( m_version = mock_version("1.0", size=123456) m_package = mock_package("example_package", m_version) - m_cache.return_value.packages = [m_package] * 10 + # Different package with version that is not candidate + m_version_2 = mock_version("2.0", size=123456) + m_package_2 = mock_package( + "example_pkg_diff_candidate_version", m_version_2 + ) + + m_package_list = [m_package] * 10 + m_package_list.append(m_package_2) + m_cache.return_value.packages = m_package_list + + m_pkg_candidate_version.return_value = "1.0" + m_filter_sec_updates.return_value = { - "esm-infra": [(m_version, "some.url.for.esm")] * 2, + "esm-infra": [ + (m_version, "some.url.for.esm"), + (m_version, "some.url.for.esm"), + (m_version_2, "some.url.for.esm"), + ], "esm-apps": [], "standard-security": [], } @@ -553,6 +570,14 @@ def test_security_status_dict( "origin": "some.url.for.esm", "download_size": 123456, }, + { + "package": "example_pkg_diff_candidate_version", + "version": "2.0", + "service_name": "esm-infra", + "status": "upgrade_available_not_preferred", + "origin": "some.url.for.esm", + "download_size": 123456, + }, ], "summary": { "ua": { @@ -560,8 +585,8 @@ def test_security_status_dict( "enabled_services": [], "entitled_services": [], }, - "num_installed_packages": 10, - "num_main_packages": 10, + "num_installed_packages": 11, + "num_main_packages": 11, "num_restricted_packages": 0, "num_universe_packages": 0, "num_multiverse_packages": 0, @@ -569,7 +594,7 @@ def test_security_status_dict( "num_unknown_packages": 0, "num_esm_infra_packages": 0, "num_esm_apps_packages": 0, - "num_esm_infra_updates": 2, + "num_esm_infra_updates": 3, "num_esm_apps_updates": 0, "num_standard_security_updates": 0, "reboot_required": "no",