Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions supervisor/addons/addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@
from ..docker.stats import DockerStats
from ..exceptions import (
AddonConfigurationError,
AddonNotSupportedError,
AddonsError,
AddonsJobError,
AddonsNotSupportedError,
ConfigurationFileError,
DockerError,
HomeAssistantAPIError,
Expand Down Expand Up @@ -1172,7 +1172,7 @@ async def stats(self) -> DockerStats:
async def write_stdin(self, data) -> None:
"""Write data to add-on stdin."""
if not self.with_stdin:
raise AddonsNotSupportedError(
raise AddonNotSupportedError(
f"Add-on {self.slug} does not support writing to stdin!", _LOGGER.error
)

Expand Down Expand Up @@ -1419,7 +1419,7 @@ def _extract_tarfile() -> tuple[TemporaryDirectory, dict[str, Any]]:

# If available
if not self._available(data[ATTR_SYSTEM]):
raise AddonsNotSupportedError(
raise AddonNotSupportedError(
f"Add-on {self.slug} is not available for this platform",
_LOGGER.error,
)
Expand Down
4 changes: 2 additions & 2 deletions supervisor/addons/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
from ..const import AddonBoot, AddonStartup, AddonState
from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import (
AddonNotSupportedError,
AddonsError,
AddonsJobError,
AddonsNotSupportedError,
CoreDNSError,
DockerError,
HassioError,
Expand Down Expand Up @@ -307,7 +307,7 @@ async def rebuild(self, slug: str, *, force: bool = False) -> asyncio.Task | Non
"Version changed, use Update instead Rebuild", _LOGGER.error
)
if not force and not addon.need_build:
raise AddonsNotSupportedError(
raise AddonNotSupportedError(
"Can't rebuild a image based add-on", _LOGGER.error
)

Expand Down
24 changes: 13 additions & 11 deletions supervisor/addons/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@
)
from ..coresys import CoreSys
from ..docker.const import Capabilities
from ..exceptions import AddonsNotSupportedError
from ..exceptions import (
AddonNotSupportedArchitectureError,
AddonNotSupportedError,
AddonNotSupportedHomeAssistantVersionError,
AddonNotSupportedMachineTypeError,
)
from ..jobs.const import JOB_GROUP_ADDON
from ..jobs.job_group import JobGroup
from ..utils import version_is_new_enough
Expand Down Expand Up @@ -680,19 +685,17 @@ def _validate_availability(
"""Validate if addon is available for current system."""
# Architecture
if not self.sys_arch.is_supported(config[ATTR_ARCH]):
raise AddonsNotSupportedError(
f"Add-on {self.slug} not supported on this platform, supported architectures: {', '.join(config[ATTR_ARCH])}",
logger,
raise AddonNotSupportedArchitectureError(
logger, slug=self.slug, architectures=config[ATTR_ARCH]
)

# Machine / Hardware
machine = config.get(ATTR_MACHINE)
if machine and (
f"!{self.sys_machine}" in machine or self.sys_machine not in machine
):
raise AddonsNotSupportedError(
f"Add-on {self.slug} not supported on this machine, supported machine types: {', '.join(machine)}",
logger,
raise AddonNotSupportedMachineTypeError(
logger, slug=self.slug, machine_types=machine
)

# Home Assistant
Expand All @@ -701,16 +704,15 @@ def _validate_availability(
if version and not version_is_new_enough(
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,
raise AddonNotSupportedHomeAssistantVersionError(
logger, slug=self.slug, version=str(version)
)

def _available(self, config) -> bool:
"""Return True if this add-on is available on this platform."""
try:
self._validate_availability(config)
except AddonsNotSupportedError:
except AddonNotSupportedError:
return False

return True
Expand Down
4 changes: 4 additions & 0 deletions supervisor/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,10 @@ def _register_store(self) -> None:
"/store/addons/{addon}/documentation",
api_store.addons_addon_documentation,
),
web.get(
"/store/addons/{addon}/availability",
api_store.addons_addon_availability,
),
web.post(
"/store/addons/{addon}/install", api_store.addons_addon_install
),
Expand Down
6 changes: 6 additions & 0 deletions supervisor/api/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,12 @@ async def addons_addon_documentation(self, request: web.Request) -> str:
_read_static_text_file, addon.path_documentation
)

@api_process
async def addons_addon_availability(self, request: web.Request) -> None:
"""Check add-on availability for current system."""
addon = cast(AddonStore, self._extract_addon(request))
addon.validate_availability()

@api_process
async def repositories_list(self, request: web.Request) -> list[dict[str, Any]]:
"""Return all repositories."""
Expand Down
14 changes: 12 additions & 2 deletions supervisor/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
HEADER_TOKEN,
HEADER_TOKEN_OLD,
JSON_DATA,
JSON_ERROR_KEY,
JSON_EXTRA_FIELDS,
JSON_JOB_ID,
JSON_MESSAGE,
JSON_MESSAGE_TEMPLATE,
JSON_RESULT,
REQUEST_FROM,
RESULT_ERROR,
Expand Down Expand Up @@ -136,10 +139,11 @@ async def wrap_api(


def api_return_error(
error: Exception | None = None,
error: HassioError | None = None,
message: str | None = None,
error_type: str | None = None,
status: int = 400,
*,
job_id: str | None = None,
) -> web.Response:
"""Return an API error message."""
Expand All @@ -158,12 +162,18 @@ def api_return_error(
body=message.encode(), content_type=error_type, status=status
)
case _:
result = {
result: dict[str, Any] = {
JSON_RESULT: RESULT_ERROR,
JSON_MESSAGE: message,
}
if job_id:
result[JSON_JOB_ID] = job_id
if error and error.error_key:
result[JSON_ERROR_KEY] = error.error_key
if error and error.message_template:
result[JSON_MESSAGE_TEMPLATE] = error.message_template
if error and error.extra_fields:
result[JSON_EXTRA_FIELDS] = error.extra_fields

return web.json_response(
result,
Expand Down
3 changes: 3 additions & 0 deletions supervisor/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@
JSON_MESSAGE = "message"
JSON_RESULT = "result"
JSON_JOB_ID = "job_id"
JSON_ERROR_KEY = "error_key"
JSON_MESSAGE_TEMPLATE = "message_template"
JSON_EXTRA_FIELDS = "extra_fields"

RESULT_ERROR = "error"
RESULT_OK = "ok"
Expand Down
91 changes: 88 additions & 3 deletions supervisor/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
"""Core Exceptions."""

from collections.abc import Callable
from typing import Any


class HassioError(Exception):
"""Root exception."""

error_key: str | None = None
message_template: str | None = None

def __init__(
self,
message: str | None = None,
logger: Callable[..., None] | None = None,
*,
extra_fields: dict[str, Any] | None = None,
) -> None:
"""Raise & log."""
self.extra_fields = extra_fields or {}

if not message and self.message_template:
message = (
self.message_template.format(**self.extra_fields)
if self.extra_fields
else self.message_template
)

if logger is not None and message is not None:
logger(message)

Expand Down Expand Up @@ -235,8 +250,71 @@ class AddonConfigurationError(AddonsError):
"""Error with add-on configuration."""


class AddonsNotSupportedError(HassioNotSupportedError):
"""Addons don't support a function."""
class AddonNotSupportedError(HassioNotSupportedError):
"""Addon doesn't support a function."""


class AddonNotSupportedArchitectureError(AddonNotSupportedError):
"""Addon does not support system due to architecture."""

error_key = "addon_not_supported_architecture_error"
message_template = "Add-on {slug} not supported on this platform, supported architectures: {architectures}"

def __init__(
self,
logger: Callable[..., None] | None = None,
*,
slug: str,
architectures: list[str],
) -> None:
"""Initialize exception."""
super().__init__(
None,
logger,
extra_fields={"slug": slug, "architectures": ", ".join(architectures)},
)


class AddonNotSupportedMachineTypeError(AddonNotSupportedError):
"""Addon does not support system due to machine type."""

error_key = "addon_not_supported_machine_type_error"
message_template = "Add-on {slug} not supported on this machine, supported machine types: {machine_types}"

def __init__(
self,
logger: Callable[..., None] | None = None,
*,
slug: str,
machine_types: list[str],
) -> None:
"""Initialize exception."""
super().__init__(
None,
logger,
extra_fields={"slug": slug, "machine_types": ", ".join(machine_types)},
)


class AddonNotSupportedHomeAssistantVersionError(AddonNotSupportedError):
"""Addon does not support system due to Home Assistant version."""

error_key = "addon_not_supported_home_assistant_version_error"
message_template = "Add-on {slug} not supported on this system, requires Home Assistant version {version} or greater"

def __init__(
self,
logger: Callable[..., None] | None = None,
*,
slug: str,
version: str,
) -> None:
"""Initialize exception."""
super().__init__(
None,
logger,
extra_fields={"slug": slug, "version": version},
)


class AddonsJobError(AddonsError, JobException):
Expand Down Expand Up @@ -319,10 +397,17 @@ def __init__(
self,
message: str | None = None,
logger: Callable[..., None] | None = None,
*,
job_id: str | None = None,
error: HassioError | None = None,
) -> None:
"""Raise & log, optionally with job."""
super().__init__(message, logger)
# Allow these to be set from another error here since APIErrors essentially wrap others to add a status
self.error_key = error.error_key if error else None
self.message_template = error.message_template if error else None
super().__init__(
message, logger, extra_fields=error.extra_fields if error else None
)
self.job_id = job_id


Expand Down
Loading