Skip to content
Open
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
7 changes: 4 additions & 3 deletions supervisor/addons/addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -1682,8 +1682,9 @@ async def watchdog_container(self, event: DockerContainerStateEvent) -> None:
]:
await self._restart_after_problem(event.state)

def refresh_path_cache(self) -> Awaitable[None]:
async def refresh_path_cache(self) -> None:
"""Refresh cache of existing paths."""
if self.is_detached or not self.addon_store:
return super().refresh_path_cache()
return self.addon_store.refresh_path_cache()
await super().refresh_path_cache()
else:
await self.addon_store.refresh_path_cache()
23 changes: 18 additions & 5 deletions supervisor/addons/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from abc import ABC, abstractmethod
from collections import defaultdict
from collections.abc import Awaitable, Callable
from collections.abc import Callable
from contextlib import suppress
from datetime import datetime
import logging
Expand Down Expand Up @@ -91,6 +91,7 @@
from ..coresys import CoreSys
from ..docker.const import Capabilities
from ..exceptions import (
AddonFileReadError,
AddonNotSupportedArchitectureError,
AddonNotSupportedError,
AddonNotSupportedHomeAssistantVersionError,
Expand Down Expand Up @@ -682,9 +683,15 @@ def read_readme() -> str | None:
# Return data
return readme.read_text(encoding="utf-8", errors="replace")

return await self.sys_run_in_executor(read_readme)

def refresh_path_cache(self) -> Awaitable[None]:
try:
return await self.sys_run_in_executor(read_readme)
except OSError as err:
self.sys_resolution.check_oserror(err)
raise AddonFileReadError(
_LOGGER.error, addon=self.slug, error=str(err)
) from err

async def refresh_path_cache(self) -> None:
"""Refresh cache of existing paths."""

def check_paths():
Expand All @@ -693,7 +700,13 @@ def check_paths():
self._path_changelog_exists = self.path_changelog.exists()
self._path_documentation_exists = self.path_documentation.exists()

return self.sys_run_in_executor(check_paths)
try:
await self.sys_run_in_executor(check_paths)
except OSError as err:
self.sys_resolution.check_oserror(err)
raise AddonFileReadError(
_LOGGER.error, addon=self.slug, error=str(err)
) from err

def validate_availability(self) -> None:
"""Validate if addon is available for current system."""
Expand Down
29 changes: 22 additions & 7 deletions supervisor/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,17 +185,32 @@ async def setup(self) -> None:

# Execute each load task in secure context
for setup_task in setup_loads:
unhealthy_before = self.sys_resolution.unhealthy.copy()
try:
await setup_task
except Exception as err: # pylint: disable=broad-except
_LOGGER.critical(
"Fatal error happening on load Task %s: %s",
setup_task,
err,
exc_info=True,
)
# If the error already caused a new unhealthy reason to
# be set (e.g. via check_oserror), it was handled by the
# resolution system and the user has been notified. Skip
# capturing to Sentry in that case.
capture_exception = self.sys_resolution.unhealthy == unhealthy_before

self.sys_resolution.add_unhealthy_reason(UnhealthyReason.SETUP)
await async_capture_exception(err)

if capture_exception:
_LOGGER.critical(
"Fatal error happening on load Task %s: %s",
setup_task,
err,
exc_info=True,
)
await async_capture_exception(err)
else:
_LOGGER.error(
"Error on load Task %s: %s",
setup_task,
err,
)

async def start(self) -> None:
"""Start Supervisor orchestration."""
Expand Down
20 changes: 20 additions & 0 deletions supervisor/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,26 @@ def __init__(
super().__init__(logger)


class AddonFileReadError(AddonsError, APIError):
"""Raise when an add-on metadata file cannot be read due to a filesystem error."""

error_key = "addon_file_read_error"
message_template = (
"Could not read metadata for add-on {addon} due to a filesystem error: {error}"
)

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


class AddonsJobError(AddonsError, JobException):
"""Raise on job errors."""

Expand Down