From 76730ef6ab7676615513e9e4aa4a850276fbe696 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 | 32 +++++++++++++++---- uaclient/messages/__init__.py | 4 +++ 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/features/api_unattended_upgrades.feature b/features/api_unattended_upgrades.feature index e306f81514..01f27d67d3 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": false, "package_lists_refresh_frequency_days": 0, "systemd_apt_timer_enabled": false, "unattended_upgrades_allowed_origins": \[\], "unattended_upgrades_disabled_reason": {"code": "unattended-upgrades-uninstalled", "msg": "unattended-upgrades packages is not installed"}, "unattended_upgrades_frequency_days": 0, "unattended_upgrades_last_run": null, "unattended_upgrades_running": false}, "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..38dbddcf45 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 False is actual_return.apt_periodic_job_enabled + assert False is actual_return.systemd_apt_timer_enabled + assert 0 == actual_return.package_lists_refresh_frequency_days + assert 0 == actual_return.unattended_upgrades_frequency_days + assert [] == actual_return.unattended_upgrades_allowed_origins + assert False 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..7f13f1dfaa 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 @@ -15,6 +15,7 @@ Field, IntDataValue, StringDataValue, + data_list, ) UNATTENDED_UPGRADES_CONFIG_KEYS = [ @@ -42,18 +43,22 @@ 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( + "package_lists_refresh_frequency_days", + IntDataValue, + ), Field("unattended_upgrades_frequency_days", IntDataValue), - Field("unattended_upgrades_allowed_origins", StringDataValue), + Field( + "unattended_upgrades_allowed_origins", + data_list(StringDataValue), + ), Field("unattended_upgrades_running", BoolDataValue), Field( "unattended_upgrades_disabled_reason", UnattendedUpgradesDisabledReason, required=False, ), - Field( - "unattended_upgrades_last_run", DatetimeDataValue, required=False - ), + Field("unattended_upgrades_last_run", DatetimeDataValue), ] def __init__( @@ -145,6 +150,21 @@ def status() -> UnattendedUpgradesStatusResult: def _status(cfg: UAConfig) -> UnattendedUpgradesStatusResult: + if not apt.is_installed("unattended-upgrades"): + return UnattendedUpgradesStatusResult( + systemd_apt_timer_enabled=False, + apt_periodic_job_enabled=False, + package_lists_refresh_frequency_days=0, + unattended_upgrades_frequency_days=0, + unattended_upgrades_allowed_origins=[], + unattended_upgrades_disabled_reason=UnattendedUpgradesDisabledReason( # noqa + msg=messages.UNATTENDED_UPGRADES_UNINSTALLED.msg, + code=messages.UNATTENDED_UPGRADES_UNINSTALLED.name, + ), + unattended_upgrades_running=False, + 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",