mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-19 15:16:33 +00:00
Clarify message when addon unavailable (#4098)
This commit is contained in:
parent
089635f4d3
commit
fed4a05003
@ -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(
|
||||||
|
@ -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,31 +600,52 @@ 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 True
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _image(self, config) -> str:
|
def _image(self, config) -> str:
|
||||||
"""Generate image name from data."""
|
"""Generate image name from data."""
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user