diff --git a/supervisor/resolution/checks/free_space.py b/supervisor/resolution/checks/free_space.py index 4b6763841..efa210e4c 100644 --- a/supervisor/resolution/checks/free_space.py +++ b/supervisor/resolution/checks/free_space.py @@ -1,15 +1,8 @@ """Helpers to check and fix issues with free space.""" -from ...backups.const import BackupType from ...const import CoreState from ...coresys import CoreSys -from ..const import ( - MINIMUM_FREE_SPACE_THRESHOLD, - MINIMUM_FULL_BACKUPS, - ContextType, - IssueType, - SuggestionType, -) +from ..const import MINIMUM_FREE_SPACE_THRESHOLD, ContextType, IssueType from .base import CheckBase @@ -23,31 +16,12 @@ class CheckFreeSpace(CheckBase): async def run_check(self) -> None: """Run check if not affected by issue.""" - if await self.sys_host.info.free_space() > MINIMUM_FREE_SPACE_THRESHOLD: - return - - suggestions: list[SuggestionType] = [] - if ( - len( - [ - backup - for backup in self.sys_backups.list_backups - if backup.sys_type == BackupType.FULL - ] - ) - > MINIMUM_FULL_BACKUPS - ): - suggestions.append(SuggestionType.CLEAR_FULL_BACKUP) - - self.sys_resolution.create_issue( - IssueType.FREE_SPACE, ContextType.SYSTEM, suggestions=suggestions - ) + if await self.approve_check(): + self.sys_resolution.create_issue(IssueType.FREE_SPACE, ContextType.SYSTEM) async def approve_check(self, reference: str | None = None) -> bool: """Approve check if it is affected by issue.""" - if await self.sys_host.info.free_space() > MINIMUM_FREE_SPACE_THRESHOLD: - return False - return True + return await self.sys_host.info.free_space() <= MINIMUM_FREE_SPACE_THRESHOLD @property def issue(self) -> IssueType: diff --git a/supervisor/resolution/const.py b/supervisor/resolution/const.py index a2fb3a97f..bad7e4f5e 100644 --- a/supervisor/resolution/const.py +++ b/supervisor/resolution/const.py @@ -9,7 +9,7 @@ FILE_CONFIG_RESOLUTION = Path(SUPERVISOR_DATA, "resolution.json") SCHEDULED_HEALTHCHECK = 3600 -MINIMUM_FREE_SPACE_THRESHOLD = 1 +MINIMUM_FREE_SPACE_THRESHOLD = 2 MINIMUM_FULL_BACKUPS = 2 DNS_CHECK_HOST = "_checkdns.home-assistant.io" diff --git a/supervisor/resolution/notify.py b/supervisor/resolution/notify.py index 1bd91b97c..13680e87b 100644 --- a/supervisor/resolution/notify.py +++ b/supervisor/resolution/notify.py @@ -10,9 +10,16 @@ from ..coresys import CoreSys, CoreSysAttributes from ..exceptions import HomeAssistantAPIError from .checks.core_security import SecurityReference from .const import ContextType, IssueType +from .data import Issue _LOGGER: logging.Logger = logging.getLogger(__name__) +ISSUE_SECURITY_CUSTOM_COMP_2021_1_5 = Issue( + IssueType.SECURITY, + ContextType.CORE, + reference=SecurityReference.CUSTOM_COMPONENTS_BELOW_2021_1_5, +) + class ResolutionNotify(CoreSysAttributes): """Notify class for resolution.""" @@ -29,44 +36,17 @@ class ResolutionNotify(CoreSysAttributes): ): return - messages = [] - - for issue in self.sys_resolution.issues: - if issue.type == IssueType.FREE_SPACE: - messages.append( - { - "title": "Available space is less than 1GB!", - "message": f"Available space is {await self.sys_host.info.free_space()}GB, see https://www.home-assistant.io/more-info/free-space for more information.", - "notification_id": "supervisor_issue_free_space", - } - ) - if issue.type == IssueType.SECURITY and issue.context == ContextType.CORE: - if ( - issue.reference - == SecurityReference.CUSTOM_COMPONENTS_BELOW_2021_1_5 - ): - messages.append( - { - "title": "Security notification", - "message": "The Supervisor detected that this version of Home Assistant could be insecure in combination with custom integrations. [Update as soon as possible.](/hassio/dashboard)\n\nFor more information see the [Security alert](https://www.home-assistant.io/latest-security-alert).", - "notification_id": "supervisor_update_home_assistant_2021_1_5", - } - ) - if issue.type == IssueType.PWNED and issue.context == ContextType.ADDON: - messages.append( - { - "title": f"Insecure secrets in {issue.reference}", - "message": f"The add-on {issue.reference} uses secrets which are detected as not secure, see https://www.home-assistant.io/more-info/pwned-passwords for more information.", - "notification_id": f"supervisor_issue_pwned_{issue.reference}", - } - ) - - for message in messages: + # This one issue must remain a persistent notification rather then a repair because repairs didn't exist in HA 2021.1.5 + if ISSUE_SECURITY_CUSTOM_COMP_2021_1_5 in self.sys_resolution.issues: try: async with self.sys_homeassistant.api.make_request( "post", "api/services/persistent_notification/create", - json=message, + json={ + "title": "Security notification", + "message": "The Supervisor detected that this version of Home Assistant could be insecure in combination with custom integrations. [Update as soon as possible.](/hassio/dashboard)\n\nFor more information see the [Security alert](https://www.home-assistant.io/latest-security-alert).", + "notification_id": "supervisor_update_home_assistant_2021_1_5", + }, ) as resp: if resp.status in (200, 201): _LOGGER.debug("Successfully created persistent_notification") diff --git a/tests/jobs/test_job_decorator.py b/tests/jobs/test_job_decorator.py index 9d20b1c0d..f0e958978 100644 --- a/tests/jobs/test_job_decorator.py +++ b/tests/jobs/test_job_decorator.py @@ -139,10 +139,10 @@ async def test_free_space(coresys: CoreSys): return True test = TestClass(coresys) - with patch("shutil.disk_usage", return_value=(42, 42, (1024.0**3))): + with patch("shutil.disk_usage", return_value=(42, 42, (2048.0**3))): assert await test.execute() - with patch("shutil.disk_usage", return_value=(42, 42, (512.0**3))): + with patch("shutil.disk_usage", return_value=(42, 42, (1024.0**3))): assert not await test.execute() coresys.jobs.ignore_conditions = [JobCondition.FREE_SPACE] diff --git a/tests/resolution/check/test_check.py b/tests/resolution/check/test_check.py index a78a290eb..c582f732e 100644 --- a/tests/resolution/check/test_check.py +++ b/tests/resolution/check/test_check.py @@ -70,7 +70,7 @@ async def test_if_check_cleanup_issue(coresys: CoreSys): assert free_space in coresys.resolution.issues - with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))): + with patch("shutil.disk_usage", return_value=(42, 42, 3 * (1024.0**3))): await coresys.resolution.check.check_system() assert free_space not in coresys.resolution.issues diff --git a/tests/resolution/check/test_check_free_space.py b/tests/resolution/check/test_check_free_space.py index fb3e3c987..de731aa02 100644 --- a/tests/resolution/check/test_check_free_space.py +++ b/tests/resolution/check/test_check_free_space.py @@ -1,33 +1,12 @@ """Test check free space fixup.""" # pylint: disable=import-error,protected-access -from unittest.mock import MagicMock, PropertyMock, patch +from unittest.mock import patch -import pytest - -from supervisor.backups.const import BackupType from supervisor.const import CoreState from supervisor.coresys import CoreSys from supervisor.resolution.checks.free_space import CheckFreeSpace -from supervisor.resolution.const import IssueType, SuggestionType - - -@pytest.fixture(name="suggestion") -async def fixture_suggestion( - coresys: CoreSys, request: pytest.FixtureRequest -) -> SuggestionType | None: - """Set up test for suggestion.""" - if request.param == SuggestionType.CLEAR_FULL_BACKUP: - backup = MagicMock() - backup.sys_type = BackupType.FULL - with patch.object( - type(coresys.backups), - "list_backups", - new=PropertyMock(return_value=[backup, backup, backup]), - ): - yield SuggestionType.CLEAR_FULL_BACKUP - else: - yield request.param +from supervisor.resolution.const import IssueType async def test_base(coresys: CoreSys): @@ -37,19 +16,14 @@ async def test_base(coresys: CoreSys): assert free_space.enabled -@pytest.mark.parametrize( - "suggestion", - [None, SuggestionType.CLEAR_FULL_BACKUP], - indirect=True, -) -async def test_check(coresys: CoreSys, suggestion: SuggestionType | None): +async def test_check(coresys: CoreSys): """Test check.""" free_space = CheckFreeSpace(coresys) await coresys.core.set_state(CoreState.RUNNING) assert len(coresys.resolution.issues) == 0 - with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))): + with patch("shutil.disk_usage", return_value=(42, 42, 3 * (1024.0**3))): await free_space.run_check() assert len(coresys.resolution.issues) == 0 @@ -58,11 +32,7 @@ async def test_check(coresys: CoreSys, suggestion: SuggestionType | None): await free_space.run_check() assert coresys.resolution.issues[-1].type == IssueType.FREE_SPACE - - if suggestion: - assert coresys.resolution.suggestions[-1].type == suggestion - else: - assert len(coresys.resolution.suggestions) == 0 + assert len(coresys.resolution.suggestions) == 0 async def test_approve(coresys: CoreSys): @@ -73,7 +43,7 @@ async def test_approve(coresys: CoreSys): with patch("shutil.disk_usage", return_value=(1, 1, 1)): assert await free_space.approve_check() - with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0**3))): + with patch("shutil.disk_usage", return_value=(42, 42, 3 * (1024.0**3))): assert not await free_space.approve_check() diff --git a/tests/store/test_store_manager.py b/tests/store/test_store_manager.py index 22fc09314..568ebfe1f 100644 --- a/tests/store/test_store_manager.py +++ b/tests/store/test_store_manager.py @@ -170,7 +170,7 @@ async def test_update_unavailable_addon( "version", new=PropertyMock(return_value=AwesomeVersion("2022.1.1")), ), - patch("shutil.disk_usage", return_value=(42, 42, (1024.0**3))), + patch("shutil.disk_usage", return_value=(42, 42, (5120.0**3))), ): with pytest.raises(AddonNotSupportedError): await coresys.addons.update("local_ssh", backup=True) @@ -226,7 +226,7 @@ async def test_install_unavailable_addon( "version", new=PropertyMock(return_value=AwesomeVersion("2022.1.1")), ), - patch("shutil.disk_usage", return_value=(42, 42, (1024.0**3))), + patch("shutil.disk_usage", return_value=(42, 42, (5120.0**3))), pytest.raises(AddonNotSupportedError), ): await coresys.addons.install("local_ssh")