From 76e05fc2211e5a9752c4496b8cc3f162a5b0259d Mon Sep 17 00:00:00 2001 From: Lucas Moura Date: Tue, 24 Oct 2023 16:28:38 -0300 Subject: [PATCH] api: fix unattended-upgrades for pkg not installed When unattended-upgrades is uninstalled in the machine, we display an error on the api endpoint that doesn't directly tell the user that the package is not installed. We are updating the logic to properly tell the user that the package is not installed. Fixes: #2807 --- features/api_unattended_upgrades.feature | 6 +++ ...est_api_u_unattended_upgrades_status_v1.py | 28 ++++++++++ .../api/u/unattended_upgrades/status/v1.py | 51 ++++++++++++++----- uaclient/messages/__init__.py | 4 ++ 4 files changed, 76 insertions(+), 13 deletions(-) diff --git a/features/api_unattended_upgrades.feature b/features/api_unattended_upgrades.feature index e306f81514..3044c99520 100644 --- a/features/api_unattended_upgrades.feature +++ b/features/api_unattended_upgrades.feature @@ -196,6 +196,12 @@ Feature: api.u.unattended_upgrades.status.v1 "vim" ] """ + When I run `apt remove unattended-upgrades -y` with sudo + And I run `pro api u.unattended_upgrades.status.v1` as non-root + Then stdout matches regexp: + """ + {"_schema_version": "v1", "data": {"attributes": {"apt_periodic_job_enabled": null, "package_lists_refresh_frequency_days": null, "systemd_apt_timer_enabled": null, "unattended_upgrades_allowed_origins": null, "unattended_upgrades_disabled_reason": {"code": "unattended-upgrades-uninstalled", "msg": "unattended-upgrades packages is not installed"}, "unattended_upgrades_frequency_days": null, "unattended_upgrades_last_run": null, "unattended_upgrades_running": null}, "meta": {"environment_vars": \[\]}, "type": "UnattendedUpgradesStatus"}, "errors": \[\], "result": "success", "version": ".*", "warnings": \[\]} + """ Examples: ubuntu release | release | extra_field | diff --git a/uaclient/api/tests/test_api_u_unattended_upgrades_status_v1.py b/uaclient/api/tests/test_api_u_unattended_upgrades_status_v1.py index 75cb456af2..be17d49867 100644 --- a/uaclient/api/tests/test_api_u_unattended_upgrades_status_v1.py +++ b/uaclient/api/tests/test_api_u_unattended_upgrades_status_v1.py @@ -8,6 +8,7 @@ UNATTENDED_UPGRADES_CFG_LIST_VALUE_EMPTY, UNATTENDED_UPGRADES_CFG_VALUE_TURNED_OFF, UNATTENDED_UPGRADES_SYSTEMD_JOB_DISABLED, + UNATTENDED_UPGRADES_UNINSTALLED, ) M_PATH = "uaclient.api.u.unattended_upgrades.status.v1" @@ -92,6 +93,7 @@ def test_unattended_upgrades_last_run_when_file_not_present( class TestUnattendedUpgradesStatusV1: + @mock.patch("uaclient.apt.is_installed", return_value=True) @mock.patch(M_PATH + ".get_apt_config_keys") @mock.patch(M_PATH + ".get_apt_config_values") @mock.patch(M_PATH + "._is_unattended_upgrades_running") @@ -104,6 +106,7 @@ def test_unattended_upgrades_status_v1( m_is_running, m_apt_cfg_values, m_apt_cfg_keys, + _m_apt_is_installed, FakeConfig, ): expected_datetime = datetime.datetime(2023, 2, 23, 15, 0, 0, 102490) @@ -154,3 +157,28 @@ def test_unattended_upgrades_status_v1( assert m_apt_job_status.call_count == 1 assert m_apt_cfg_values.call_count == 1 assert m_last_run.call_count == 1 + assert _m_apt_is_installed.call_count == 1 + + @mock.patch("uaclient.apt.is_installed", return_value=False) + def test_unattended_upgrades_status_v1_when_pkg_not_installed( + self, + _m_apt_is_installed, + FakeConfig, + ): + actual_return = api._status(FakeConfig()) + assert None is actual_return.apt_periodic_job_enabled + assert None is actual_return.systemd_apt_timer_enabled + assert None is actual_return.package_lists_refresh_frequency_days + assert None is actual_return.unattended_upgrades_frequency_days + assert None is actual_return.unattended_upgrades_allowed_origins + assert None is actual_return.unattended_upgrades_running + assert None is actual_return.unattended_upgrades_last_run + assert ( + api.UnattendedUpgradesDisabledReason( + msg=UNATTENDED_UPGRADES_UNINSTALLED.msg, + code=UNATTENDED_UPGRADES_UNINSTALLED.name, + ) + == actual_return.unattended_upgrades_disabled_reason + ) + + assert _m_apt_is_installed.call_count == 1 diff --git a/uaclient/api/u/unattended_upgrades/status/v1.py b/uaclient/api/u/unattended_upgrades/status/v1.py index 170a75c947..56d987f726 100644 --- a/uaclient/api/u/unattended_upgrades/status/v1.py +++ b/uaclient/api/u/unattended_upgrades/status/v1.py @@ -2,7 +2,7 @@ import os from typing import Dict, List, Optional, Tuple, Union -from uaclient import exceptions, messages, system +from uaclient import apt, exceptions, messages, system from uaclient.api.api import APIEndpoint from uaclient.api.data_types import AdditionalInfo from uaclient.api.exceptions import UnattendedUpgradesError @@ -40,12 +40,22 @@ def __init__(self, msg: str, code: str): class UnattendedUpgradesStatusResult(DataObject, AdditionalInfo): fields = [ - Field("systemd_apt_timer_enabled", BoolDataValue), - Field("apt_periodic_job_enabled", BoolDataValue), - Field("package_lists_refresh_frequency_days", IntDataValue), - Field("unattended_upgrades_frequency_days", IntDataValue), - Field("unattended_upgrades_allowed_origins", StringDataValue), - Field("unattended_upgrades_running", BoolDataValue), + Field("systemd_apt_timer_enabled", BoolDataValue, required=False), + Field("apt_periodic_job_enabled", BoolDataValue, required=False), + Field( + "package_lists_refresh_frequency_days", + IntDataValue, + required=False, + ), + Field( + "unattended_upgrades_frequency_days", IntDataValue, required=False + ), + Field( + "unattended_upgrades_allowed_origins", + StringDataValue, + required=False, + ), + Field("unattended_upgrades_running", BoolDataValue, required=False), Field( "unattended_upgrades_disabled_reason", UnattendedUpgradesDisabledReason, @@ -59,12 +69,12 @@ class UnattendedUpgradesStatusResult(DataObject, AdditionalInfo): def __init__( self, *, - systemd_apt_timer_enabled: bool, - apt_periodic_job_enabled: bool, - package_lists_refresh_frequency_days: int, - unattended_upgrades_frequency_days: int, - unattended_upgrades_allowed_origins: List[str], - unattended_upgrades_running: bool, + systemd_apt_timer_enabled: Optional[bool], + apt_periodic_job_enabled: Optional[bool], + package_lists_refresh_frequency_days: Optional[int], + unattended_upgrades_frequency_days: Optional[int], + unattended_upgrades_allowed_origins: Optional[List[str]], + unattended_upgrades_running: Optional[bool], unattended_upgrades_disabled_reason: Optional[ UnattendedUpgradesDisabledReason ], @@ -145,6 +155,21 @@ def status() -> UnattendedUpgradesStatusResult: def _status(cfg: UAConfig) -> UnattendedUpgradesStatusResult: + if not apt.is_installed("unattended-upgrades"): + return UnattendedUpgradesStatusResult( + systemd_apt_timer_enabled=None, + apt_periodic_job_enabled=None, + package_lists_refresh_frequency_days=None, + unattended_upgrades_frequency_days=None, + unattended_upgrades_allowed_origins=None, + unattended_upgrades_disabled_reason=UnattendedUpgradesDisabledReason( # noqa + msg=messages.UNATTENDED_UPGRADES_UNINSTALLED.msg, + code=messages.UNATTENDED_UPGRADES_UNINSTALLED.name, + ), + unattended_upgrades_running=None, + unattended_upgrades_last_run=None, + ) + systemd_apt_timer_enabled = _get_apt_daily_job_status() unattended_upgrades_last_run = _get_unattended_upgrades_last_run() diff --git a/uaclient/messages/__init__.py b/uaclient/messages/__init__.py index 37b85e017e..5c18ac0436 100644 --- a/uaclient/messages/__init__.py +++ b/uaclient/messages/__init__.py @@ -1850,6 +1850,10 @@ def __repr__(self): "unattended-upgrades-cfg-value-turned-off", t.gettext("{cfg_name} is turned off"), ) +UNATTENDED_UPGRADES_UNINSTALLED = NamedMessage( + "unattended-upgrades-uninstalled", + "unattended-upgrades packages is not installed", +) LANDSCAPE_CLIENT_NOT_INSTALLED = NamedMessage( "landscape-client-not-installed",