mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-20 15:46:29 +00:00
Add core version check (#2436)
* Add core version check * Fix version handling, rename and use correct paths * simplify check * wrap approve check
This commit is contained in:
parent
e1068997ea
commit
88eb9511bf
@ -4,6 +4,7 @@ from typing import List
|
|||||||
|
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from .checks.base import CheckBase
|
from .checks.base import CheckBase
|
||||||
|
from .checks.core_security import CheckCoreSecurity
|
||||||
from .checks.free_space import CheckFreeSpace
|
from .checks.free_space import CheckFreeSpace
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
@ -16,12 +17,13 @@ class ResolutionCheck(CoreSysAttributes):
|
|||||||
"""Initialize the checks class."""
|
"""Initialize the checks class."""
|
||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
|
|
||||||
|
self._core_security = CheckCoreSecurity(coresys)
|
||||||
self._free_space = CheckFreeSpace(coresys)
|
self._free_space = CheckFreeSpace(coresys)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def all_tests(self) -> List[CheckBase]:
|
def all_tests(self) -> List[CheckBase]:
|
||||||
"""Return all list of all checks."""
|
"""Return all list of all checks."""
|
||||||
return [self._free_space]
|
return [self._core_security, self._free_space]
|
||||||
|
|
||||||
async def check_system(self) -> None:
|
async def check_system(self) -> None:
|
||||||
"""Check the system."""
|
"""Check the system."""
|
||||||
|
62
supervisor/resolution/checks/core_security.py
Normal file
62
supervisor/resolution/checks/core_security.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
"""Helpers to check core security."""
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from awesomeversion import AwesomeVersion, AwesomeVersionException
|
||||||
|
|
||||||
|
from ...const import CoreState
|
||||||
|
from ..const import ContextType, IssueType, SuggestionType
|
||||||
|
from .base import CheckBase
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityReference(str, Enum):
|
||||||
|
"""Version references."""
|
||||||
|
|
||||||
|
CUSTOM_COMPONENTS_BELOW_2021_1_3 = "custom_components_below_2021_1_3"
|
||||||
|
|
||||||
|
|
||||||
|
class CheckCoreSecurity(CheckBase):
|
||||||
|
"""CheckCoreSecurity class for check."""
|
||||||
|
|
||||||
|
async def run_check(self) -> None:
|
||||||
|
"""Run check if not affected by issue."""
|
||||||
|
try:
|
||||||
|
if self.sys_homeassistant.version < AwesomeVersion("2021.1.3"):
|
||||||
|
if Path(
|
||||||
|
self.sys_config.path_homeassistant, "custom_components"
|
||||||
|
).exists():
|
||||||
|
self.sys_resolution.create_issue(
|
||||||
|
IssueType.SECURITY,
|
||||||
|
ContextType.CORE,
|
||||||
|
reference=SecurityReference.CUSTOM_COMPONENTS_BELOW_2021_1_3,
|
||||||
|
suggestions=[SuggestionType.EXECUTE_UPDATE],
|
||||||
|
)
|
||||||
|
except AwesomeVersionException:
|
||||||
|
return
|
||||||
|
|
||||||
|
async def approve_check(self) -> bool:
|
||||||
|
"""Approve check if it is affected by issue."""
|
||||||
|
try:
|
||||||
|
if self.sys_homeassistant.version >= AwesomeVersion("2021.1.3"):
|
||||||
|
return False
|
||||||
|
except AwesomeVersionException:
|
||||||
|
return True
|
||||||
|
if not Path(self.sys_config.path_homeassistant, "custom_components").exists():
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def issue(self) -> IssueType:
|
||||||
|
"""Return a IssueType enum."""
|
||||||
|
return IssueType.SECURITY
|
||||||
|
|
||||||
|
@property
|
||||||
|
def context(self) -> ContextType:
|
||||||
|
"""Return a ContextType enum."""
|
||||||
|
return ContextType.CORE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def states(self) -> List[CoreState]:
|
||||||
|
"""Return a list of valid states when this check can run."""
|
||||||
|
return [CoreState.RUNNING, CoreState.STARTUP]
|
@ -50,6 +50,7 @@ class IssueType(str, Enum):
|
|||||||
DOCKER_RATELIMIT = "docker_ratelimit"
|
DOCKER_RATELIMIT = "docker_ratelimit"
|
||||||
CORRUPT_DOCKER = "corrupt_docker"
|
CORRUPT_DOCKER = "corrupt_docker"
|
||||||
CORRUPT_REPOSITORY = "corrupt_repository"
|
CORRUPT_REPOSITORY = "corrupt_repository"
|
||||||
|
SECURITY = "security"
|
||||||
MISSING_IMAGE = "missing_image"
|
MISSING_IMAGE = "missing_image"
|
||||||
UPDATE_FAILED = "update_failed"
|
UPDATE_FAILED = "update_failed"
|
||||||
UPDATE_ROLLBACK = "update_rollback"
|
UPDATE_ROLLBACK = "update_rollback"
|
||||||
|
@ -8,7 +8,8 @@ import logging
|
|||||||
|
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from ..exceptions import HomeAssistantAPIError
|
from ..exceptions import HomeAssistantAPIError
|
||||||
from .const import IssueType
|
from .checks.core_security import SecurityReference
|
||||||
|
from .const import ContextType, IssueType
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -39,6 +40,18 @@ class ResolutionNotify(CoreSysAttributes):
|
|||||||
"notification_id": "supervisor_issue_free_space",
|
"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_3
|
||||||
|
):
|
||||||
|
messages.append(
|
||||||
|
{
|
||||||
|
"title": "Security notification",
|
||||||
|
"message": "The Supervisor detected that this version of Home Assistant is insecure. [Update as soon as possible.](/hassio/dashboard)\n\nFor more information see the [Security bulletin](https://www.home-assistant.io/blog/2021/01/14/security-bulletin/).",
|
||||||
|
"notification_id": "supervisor_update_home_assistant_2021_1_3",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
for message in messages:
|
for message in messages:
|
||||||
try:
|
try:
|
||||||
|
84
tests/resolution/check/test_check_core_security.py
Normal file
84
tests/resolution/check/test_check_core_security.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
"""Test core version check."""
|
||||||
|
# pylint: disable=import-error,protected-access
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from awesomeversion import AwesomeVersion
|
||||||
|
|
||||||
|
from supervisor.const import CoreState
|
||||||
|
from supervisor.coresys import CoreSys
|
||||||
|
from supervisor.resolution.checks.core_security import CheckCoreSecurity
|
||||||
|
from supervisor.resolution.const import IssueType
|
||||||
|
|
||||||
|
|
||||||
|
async def test_check(coresys: CoreSys, tmp_path):
|
||||||
|
"""Test check."""
|
||||||
|
with patch("supervisor.config.CoreConfig.path_homeassistant", tmp_path):
|
||||||
|
core_security = CheckCoreSecurity(coresys)
|
||||||
|
coresys.core.state = CoreState.RUNNING
|
||||||
|
|
||||||
|
assert len(coresys.resolution.issues) == 0
|
||||||
|
|
||||||
|
coresys.homeassistant._data["version"] = AwesomeVersion("2021.12.1")
|
||||||
|
await core_security.run_check()
|
||||||
|
assert len(coresys.resolution.issues) == 0
|
||||||
|
|
||||||
|
coresys.homeassistant._data["version"] = AwesomeVersion("landingpage")
|
||||||
|
await core_security.run_check()
|
||||||
|
assert len(coresys.resolution.issues) == 0
|
||||||
|
|
||||||
|
coresys.homeassistant._data["version"] = AwesomeVersion(None)
|
||||||
|
await core_security.run_check()
|
||||||
|
assert len(coresys.resolution.issues) == 0
|
||||||
|
|
||||||
|
coresys.homeassistant._data["version"] = AwesomeVersion("2021.1.2")
|
||||||
|
await core_security.run_check()
|
||||||
|
assert len(coresys.resolution.issues) == 0
|
||||||
|
|
||||||
|
Path(coresys.config.path_homeassistant, "custom_components").mkdir(parents=True)
|
||||||
|
await core_security.run_check()
|
||||||
|
|
||||||
|
assert coresys.resolution.issues[-1].type == IssueType.SECURITY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_approve(coresys: CoreSys, tmp_path):
|
||||||
|
"""Test check."""
|
||||||
|
with patch("supervisor.config.CoreConfig.path_homeassistant", tmp_path):
|
||||||
|
core_security = CheckCoreSecurity(coresys)
|
||||||
|
coresys.core.state = CoreState.RUNNING
|
||||||
|
coresys.homeassistant._data["version"] = None
|
||||||
|
assert await core_security.approve_check()
|
||||||
|
|
||||||
|
coresys.homeassistant._data["version"] = AwesomeVersion("2021.1.3")
|
||||||
|
assert not await core_security.approve_check()
|
||||||
|
|
||||||
|
coresys.homeassistant._data["version"] = AwesomeVersion("2021.1.2")
|
||||||
|
assert not await core_security.approve_check()
|
||||||
|
|
||||||
|
Path(coresys.config.path_homeassistant, "custom_components").mkdir(parents=True)
|
||||||
|
assert await core_security.approve_check()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_did_run(coresys: CoreSys):
|
||||||
|
"""Test that the check ran as expected."""
|
||||||
|
core_security = CheckCoreSecurity(coresys)
|
||||||
|
should_run = core_security.states
|
||||||
|
should_not_run = [state for state in CoreState if state not in should_run]
|
||||||
|
assert len(should_run) != 0
|
||||||
|
assert len(should_not_run) != 0
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"supervisor.resolution.checks.core_security.CheckCoreSecurity.run_check",
|
||||||
|
return_value=None,
|
||||||
|
) as check:
|
||||||
|
for state in should_run:
|
||||||
|
coresys.core.state = state
|
||||||
|
await core_security()
|
||||||
|
check.assert_called_once()
|
||||||
|
check.reset_mock()
|
||||||
|
|
||||||
|
for state in should_not_run:
|
||||||
|
coresys.core.state = state
|
||||||
|
await core_security()
|
||||||
|
check.assert_not_called()
|
||||||
|
check.reset_mock()
|
Loading…
x
Reference in New Issue
Block a user