Clarify message when addon unavailable (#4098)

This commit is contained in:
Mike Degatano 2023-01-12 11:46:40 -05:00 committed by GitHub
parent 089635f4d3
commit fed4a05003
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 166 additions and 19 deletions

View File

@ -158,10 +158,7 @@ class AddonManager(CoreSysAttributes):
if not store: if not store:
raise AddonsError(f"Add-on {slug} does not exist", _LOGGER.error) raise AddonsError(f"Add-on {slug} does not exist", _LOGGER.error)
if not store.available: store.validate_availability()
raise AddonsNotSupportedError(
f"Add-on {slug} not supported on this platform", _LOGGER.error
)
self.data.install(store) self.data.install(store)
addon = Addon(self.coresys, slug) addon = Addon(self.coresys, slug)
@ -263,10 +260,7 @@ class AddonManager(CoreSysAttributes):
raise AddonsError(f"No update available for add-on {slug}", _LOGGER.warning) raise AddonsError(f"No update available for add-on {slug}", _LOGGER.warning)
# Check if available, Maybe something have changed # Check if available, Maybe something have changed
if not store.available: store.validate_availability()
raise AddonsNotSupportedError(
f"Add-on {slug} not supported on that platform", _LOGGER.error
)
if backup: if backup:
await self.sys_backups.do_backup_partial( await self.sys_backups.do_backup_partial(

View File

@ -1,6 +1,8 @@
"""Init file for Supervisor add-ons.""" """Init file for Supervisor add-ons."""
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections.abc import Awaitable from collections.abc import Awaitable
from contextlib import suppress
import logging
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -78,10 +80,13 @@ from ..const import (
) )
from ..coresys import CoreSys, CoreSysAttributes from ..coresys import CoreSys, CoreSysAttributes
from ..docker.const import Capabilities from ..docker.const import Capabilities
from ..exceptions import AddonsNotSupportedError
from .const import ATTR_BACKUP, ATTR_CODENOTARY, AddonBackupMode from .const import ATTR_BACKUP, ATTR_CODENOTARY, AddonBackupMode
from .options import AddonOptions, UiOptions from .options import AddonOptions, UiOptions
from .validate import RE_SERVICE, RE_VOLUME from .validate import RE_SERVICE, RE_VOLUME
_LOGGER: logging.Logger = logging.getLogger(__name__)
Data = dict[str, Any] Data = dict[str, Any]
@ -595,30 +600,51 @@ class AddonModel(CoreSysAttributes, ABC):
"""Return Signer email address for CAS.""" """Return Signer email address for CAS."""
return self.data.get(ATTR_CODENOTARY) return self.data.get(ATTR_CODENOTARY)
def validate_availability(self) -> None:
"""Validate if addon is available for current system."""
return self._validate_availability(self.data)
def __eq__(self, other): def __eq__(self, other):
"""Compaired add-on objects.""" """Compaired add-on objects."""
if not isinstance(other, AddonModel): if not isinstance(other, AddonModel):
return False return False
return self.slug == other.slug return self.slug == other.slug
def _available(self, config) -> bool: def _validate_availability(self, config) -> None:
"""Return True if this add-on is available on this platform.""" """Validate if addon is available for current system."""
# Architecture # Architecture
if not self.sys_arch.is_supported(config[ATTR_ARCH]): if not self.sys_arch.is_supported(config[ATTR_ARCH]):
return False raise AddonsNotSupportedError(
f"Add-on {self.slug} not supported on this platform, supported architectures: {', '.join(config[ATTR_ARCH])}",
_LOGGER.error,
)
# Machine / Hardware # Machine / Hardware
machine = config.get(ATTR_MACHINE) machine = config.get(ATTR_MACHINE)
if machine and f"!{self.sys_machine}" in machine: if machine and (
return False f"!{self.sys_machine}" in machine or self.sys_machine not in machine
elif machine and self.sys_machine not in machine: ):
return False raise AddonsNotSupportedError(
f"Add-on {self.slug} not supported on this machine, supported machine types: {', '.join(machine)}",
_LOGGER.error,
)
# Home Assistant # Home Assistant
version: AwesomeVersion | None = config.get(ATTR_HOMEASSISTANT) version: AwesomeVersion | None = config.get(ATTR_HOMEASSISTANT)
with suppress(AwesomeVersionException, TypeError):
if self.sys_homeassistant.version < version:
raise AddonsNotSupportedError(
f"Add-on {self.slug} not supported on this system, requires Home Assistant version {version} or greater",
_LOGGER.error,
)
def _available(self, config) -> bool:
"""Return True if this add-on is available on this platform."""
try: try:
return self.sys_homeassistant.version >= version self._validate_availability(config)
except (AwesomeVersionException, TypeError): except AddonsNotSupportedError:
return False
return True return True
def _image(self, config) -> str: def _image(self, config) -> str:

View File

@ -8,6 +8,7 @@ from dbus_fast.introspection import Method, Property, Signal
from supervisor.dbus.interface import DBusInterface, DBusInterfaceProxy from supervisor.dbus.interface import DBusInterface, DBusInterfaceProxy
from supervisor.utils.dbus import DBUS_INTERFACE_PROPERTIES from supervisor.utils.dbus import DBUS_INTERFACE_PROPERTIES
from supervisor.utils.yaml import read_yaml_file
def get_dbus_name(intr_list: list[Method | Property | Signal], snake_case: str) -> str: def get_dbus_name(intr_list: list[Method | Property | Signal], snake_case: str) -> str:
@ -71,6 +72,12 @@ def load_json_fixture(filename: str) -> Any:
return json.loads(path.read_text(encoding="utf-8")) return json.loads(path.read_text(encoding="utf-8"))
def load_yaml_fixture(filename: str) -> Any:
"""Load a YAML fixture."""
path = Path(Path(__file__).parent.joinpath("fixtures"), filename)
return read_yaml_file(path)
def load_fixture(filename: str) -> str: def load_fixture(filename: str) -> str:
"""Load a fixture.""" """Load a fixture."""
path = Path(Path(__file__).parent.joinpath("fixtures"), filename) path = Path(Path(__file__).parent.joinpath("fixtures"), filename)

View File

@ -1,15 +1,24 @@
"""Test store manager.""" """Test store manager."""
from typing import Any
from unittest.mock import PropertyMock, patch from unittest.mock import PropertyMock, patch
from awesomeversion import AwesomeVersion
import pytest import pytest
from supervisor.addons.addon import Addon
from supervisor.arch import CpuArch
from supervisor.backups.manager import BackupManager
from supervisor.bootstrap import migrate_system_env from supervisor.bootstrap import migrate_system_env
from supervisor.const import ATTR_ADDONS_CUSTOM_LIST from supervisor.const import ATTR_ADDONS_CUSTOM_LIST
from supervisor.coresys import CoreSys from supervisor.coresys import CoreSys
from supervisor.exceptions import StoreJobError from supervisor.exceptions import AddonsNotSupportedError, StoreJobError
from supervisor.homeassistant.module import HomeAssistant
from supervisor.store import StoreManager from supervisor.store import StoreManager
from supervisor.store.addon import AddonStore
from supervisor.store.repository import Repository from supervisor.store.repository import Repository
from tests.common import load_yaml_fixture
async def test_default_load(coresys: CoreSys): async def test_default_load(coresys: CoreSys):
"""Test default load from config.""" """Test default load from config."""
@ -111,3 +120,114 @@ async def test_reload_fails_if_out_of_date(coresys: CoreSys):
type(coresys.supervisor), "need_update", new=PropertyMock(return_value=True) type(coresys.supervisor), "need_update", new=PropertyMock(return_value=True)
), pytest.raises(StoreJobError): ), pytest.raises(StoreJobError):
await coresys.store.reload() await coresys.store.reload()
@pytest.mark.parametrize(
"config,log",
[
(
{"arch": ["i386"]},
"Add-on local_ssh not supported on this platform, supported architectures: i386",
),
(
{"machine": ["odroid-n2"]},
"Add-on local_ssh not supported on this machine, supported machine types: odroid-n2",
),
(
{"machine": ["!qemux86-64"]},
"Add-on local_ssh not supported on this machine, supported machine types: !qemux86-64",
),
(
{"homeassistant": AwesomeVersion("2023.1.1")},
"Add-on local_ssh not supported on this system, requires Home Assistant version 2023.1.1 or greater",
),
],
)
async def test_update_unavailable_addon(
coresys: CoreSys,
install_addon_ssh: Addon,
caplog: pytest.LogCaptureFixture,
config: dict[str, Any],
log: str,
):
"""Test updating addon when new version not available for system."""
addon_config = dict(
load_yaml_fixture("addons/local/ssh/config.yaml"),
version=AwesomeVersion("10.0.0"),
**config,
)
with patch.object(BackupManager, "do_backup_partial") as backup, patch.object(
AddonStore, "data", new=PropertyMock(return_value=addon_config)
), patch.object(
CpuArch, "supported", new=PropertyMock(return_value=["amd64"])
), patch.object(
CoreSys, "machine", new=PropertyMock(return_value="qemux86-64")
), patch.object(
HomeAssistant,
"version",
new=PropertyMock(return_value=AwesomeVersion("2022.1.1")),
), patch(
"shutil.disk_usage", return_value=(42, 42, (1024.0**3))
):
with pytest.raises(AddonsNotSupportedError):
await coresys.addons.update("local_ssh", backup=True)
backup.assert_not_called()
assert log in caplog.text
@pytest.mark.parametrize(
"config,log",
[
(
{"arch": ["i386"]},
"Add-on local_ssh not supported on this platform, supported architectures: i386",
),
(
{"machine": ["odroid-n2"]},
"Add-on local_ssh not supported on this machine, supported machine types: odroid-n2",
),
(
{"machine": ["!qemux86-64"]},
"Add-on local_ssh not supported on this machine, supported machine types: !qemux86-64",
),
(
{"homeassistant": AwesomeVersion("2023.1.1")},
"Add-on local_ssh not supported on this system, requires Home Assistant version 2023.1.1 or greater",
),
],
)
async def test_install_unavailable_addon(
coresys: CoreSys,
repository: Repository,
caplog: pytest.LogCaptureFixture,
config: dict[str, Any],
log: str,
):
"""Test updating addon when new version not available for system."""
addon_config = dict(
load_yaml_fixture("addons/local/ssh/config.yaml"),
version=AwesomeVersion("10.0.0"),
**config,
)
with patch.object(
AddonStore, "data", new=PropertyMock(return_value=addon_config)
), patch.object(
CpuArch, "supported", new=PropertyMock(return_value=["amd64"])
), patch.object(
CoreSys, "machine", new=PropertyMock(return_value="qemux86-64")
), patch.object(
HomeAssistant,
"version",
new=PropertyMock(return_value=AwesomeVersion("2022.1.1")),
), patch(
"shutil.disk_usage", return_value=(42, 42, (1024.0**3))
), pytest.raises(
AddonsNotSupportedError
):
await coresys.addons.install("local_ssh")
assert log in caplog.text