mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-04-17 15:23:21 +01:00
* Add specific error message for registry authentication failures
When a Docker image pull fails with 401 Unauthorized and registry
credentials are configured, raise DockerRegistryAuthError instead of
a generic DockerError. This surfaces a clear message to the user
("Docker registry authentication failed for <registry>. Check your
registry credentials") instead of "An unknown error occurred with
addon <name>".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add tests for registry authentication error handling
Test that a 401 during image pull raises DockerRegistryAuthError when
credentials are configured, and falls back to generic DockerError
when no credentials are present.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add tests for addon install/update/rebuild auth failure handling
Test that DockerRegistryAuthError propagates correctly through
addon install, update, and rebuild paths without being wrapped
in a generic AddonUnknownError.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1210 lines
30 KiB
Python
1210 lines
30 KiB
Python
"""Core Exceptions."""
|
|
|
|
from collections.abc import Callable, Mapping
|
|
from typing import Any
|
|
|
|
from .const import OBSERVER_PORT
|
|
|
|
MESSAGE_CHECK_SUPERVISOR_LOGS = (
|
|
"Check supervisor logs for details (check with '{logs_command}')"
|
|
)
|
|
EXTRA_FIELDS_LOGS_COMMAND = {"logs_command": "ha supervisor logs"}
|
|
|
|
|
|
class HassioError(Exception):
|
|
"""Root exception."""
|
|
|
|
error_key: str | None = None
|
|
message_template: str | None = None
|
|
extra_fields: dict[str, Any] | None = None
|
|
|
|
def __init__(
|
|
self, message: str | None = None, logger: Callable[..., None] | None = None
|
|
) -> None:
|
|
"""Raise & log."""
|
|
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)
|
|
|
|
# Init Base
|
|
if message is not None:
|
|
super().__init__(message)
|
|
else:
|
|
super().__init__()
|
|
|
|
|
|
class HassioNotSupportedError(HassioError):
|
|
"""Function is not supported."""
|
|
|
|
|
|
# API
|
|
|
|
|
|
class APIError(HassioError):
|
|
"""API errors."""
|
|
|
|
status = 400
|
|
headers: Mapping[str, str] | None = None
|
|
|
|
def __init__(
|
|
self,
|
|
message: str | None = None,
|
|
logger: Callable[..., None] | None = None,
|
|
*,
|
|
headers: Mapping[str, str] | None = None,
|
|
job_id: str | None = None,
|
|
) -> None:
|
|
"""Raise & log, optionally with job."""
|
|
super().__init__(message, logger)
|
|
self.headers = headers
|
|
self.job_id = job_id
|
|
|
|
|
|
class APIUnauthorized(APIError):
|
|
"""API unauthorized error."""
|
|
|
|
status = 401
|
|
|
|
|
|
class APIForbidden(APIError):
|
|
"""API forbidden error."""
|
|
|
|
status = 403
|
|
|
|
|
|
class APINotFound(APIError):
|
|
"""API not found error."""
|
|
|
|
status = 404
|
|
|
|
|
|
class APIGone(APIError):
|
|
"""API is no longer available."""
|
|
|
|
status = 410
|
|
|
|
|
|
class APITooManyRequests(APIError):
|
|
"""API too many requests error."""
|
|
|
|
status = 429
|
|
|
|
|
|
class APIInternalServerError(APIError):
|
|
"""API internal server error."""
|
|
|
|
status = 500
|
|
|
|
|
|
class APIAddonNotInstalled(APIError):
|
|
"""Not installed addon requested at addons API."""
|
|
|
|
|
|
class APIDBMigrationInProgress(APIError):
|
|
"""Service is unavailable due to an offline DB migration is in progress."""
|
|
|
|
status = 503
|
|
|
|
|
|
class APIUnknownSupervisorError(APIError):
|
|
"""Unknown error occurred within supervisor. Adds supervisor check logs rider to message template."""
|
|
|
|
status = 500
|
|
|
|
def __init__(
|
|
self,
|
|
logger: Callable[..., None] | None = None,
|
|
*,
|
|
job_id: str | None = None,
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.message_template = (
|
|
f"{self.message_template}. {MESSAGE_CHECK_SUPERVISOR_LOGS}"
|
|
)
|
|
self.extra_fields = (self.extra_fields or {}) | EXTRA_FIELDS_LOGS_COMMAND
|
|
super().__init__(None, logger, job_id=job_id)
|
|
|
|
|
|
# JobManager
|
|
|
|
|
|
class JobException(HassioError):
|
|
"""Base job exception."""
|
|
|
|
|
|
class JobConditionException(JobException):
|
|
"""Exception happening for job conditions."""
|
|
|
|
|
|
class JobStartException(JobException):
|
|
"""Exception occurred starting a job on in current asyncio task."""
|
|
|
|
|
|
class JobNotFound(JobException):
|
|
"""Exception for job not found."""
|
|
|
|
|
|
class JobInvalidUpdate(JobException):
|
|
"""Exception for invalid update to a job."""
|
|
|
|
|
|
class JobGroupExecutionLimitExceeded(JobException):
|
|
"""Exception when job group execution limit exceeded."""
|
|
|
|
|
|
# HomeAssistant
|
|
|
|
|
|
class HomeAssistantError(HassioError):
|
|
"""Home Assistant exception."""
|
|
|
|
|
|
class HomeAssistantUpdateError(HomeAssistantError):
|
|
"""Error on update of a Home Assistant."""
|
|
|
|
|
|
class HomeAssistantCrashError(HomeAssistantError):
|
|
"""Error on crash of a Home Assistant startup."""
|
|
|
|
|
|
class HomeAssistantStartupTimeout(HomeAssistantCrashError):
|
|
"""Timeout waiting for Home Assistant successful startup."""
|
|
|
|
|
|
class HomeAssistantAPIError(HomeAssistantError):
|
|
"""Home Assistant API exception."""
|
|
|
|
|
|
class HomeAssistantAuthError(HomeAssistantAPIError):
|
|
"""Home Assistant Auth API exception."""
|
|
|
|
|
|
class HomeAssistantWSError(HomeAssistantAPIError):
|
|
"""Home Assistant websocket error."""
|
|
|
|
|
|
class HomeAssistantWSConnectionError(HomeAssistantWSError):
|
|
"""Raise when the WebSocket connection has an error."""
|
|
|
|
|
|
class HomeAssistantJobError(HomeAssistantError, JobException):
|
|
"""Raise on Home Assistant job error."""
|
|
|
|
|
|
# Supervisor
|
|
|
|
|
|
class SupervisorError(HassioError):
|
|
"""Supervisor error."""
|
|
|
|
|
|
class SupervisorUpdateError(SupervisorError):
|
|
"""Supervisor update error."""
|
|
|
|
|
|
class SupervisorAppArmorError(SupervisorError):
|
|
"""Supervisor AppArmor error."""
|
|
|
|
|
|
class SupervisorUnknownError(SupervisorError, APIUnknownSupervisorError):
|
|
"""Raise when an unknown error occurs interacting with Supervisor or its container."""
|
|
|
|
error_key = "supervisor_unknown_error"
|
|
message_template = "An unknown error occurred with Supervisor"
|
|
|
|
|
|
class SupervisorJobError(SupervisorError, JobException):
|
|
"""Raise on job errors."""
|
|
|
|
|
|
# HassOS
|
|
|
|
|
|
class HassOSError(HassioError):
|
|
"""HassOS exception."""
|
|
|
|
|
|
class HassOSUpdateError(HassOSError):
|
|
"""Error on update of a HassOS."""
|
|
|
|
|
|
class HassOSJobError(HassOSError, JobException):
|
|
"""Function not supported by HassOS."""
|
|
|
|
|
|
class HassOSDataDiskError(HassOSError):
|
|
"""Issues with the DataDisk feature from HAOS."""
|
|
|
|
|
|
class HassOSSlotNotFound(HassOSError):
|
|
"""Could not find boot slot."""
|
|
|
|
|
|
class HassOSSlotUpdateError(HassOSError):
|
|
"""Error while updating a slot via rauc."""
|
|
|
|
|
|
# All Plugins
|
|
|
|
|
|
class PluginError(HassioError):
|
|
"""Plugin error."""
|
|
|
|
|
|
class PluginJobError(PluginError, JobException):
|
|
"""Raise on job error with plugin."""
|
|
|
|
|
|
# HaCli
|
|
|
|
|
|
class CliError(PluginError):
|
|
"""HA cli exception."""
|
|
|
|
|
|
class CliUpdateError(CliError):
|
|
"""Error on update of a HA cli."""
|
|
|
|
|
|
class CliJobError(CliError, PluginJobError):
|
|
"""Raise on job error with cli plugin."""
|
|
|
|
|
|
# Observer
|
|
|
|
|
|
class ObserverError(PluginError):
|
|
"""General Observer exception."""
|
|
|
|
|
|
class ObserverUpdateError(ObserverError):
|
|
"""Error on update of a Observer."""
|
|
|
|
|
|
class ObserverJobError(ObserverError, PluginJobError):
|
|
"""Raise on job error with observer plugin."""
|
|
|
|
|
|
class ObserverPortConflict(ObserverError, APIError):
|
|
"""Raise if observer cannot start due to a port conflict."""
|
|
|
|
error_key = "observer_port_conflict"
|
|
message_template = "Cannot start {observer} because port {port} is already in use"
|
|
extra_fields = {"observer": "observer", "port": OBSERVER_PORT}
|
|
|
|
def __init__(self, logger: Callable[..., None] | None = None) -> None:
|
|
"""Raise & log."""
|
|
super().__init__(None, logger)
|
|
|
|
|
|
# Multicast
|
|
|
|
|
|
class MulticastError(PluginError):
|
|
"""Multicast exception."""
|
|
|
|
|
|
class MulticastUpdateError(MulticastError):
|
|
"""Error on update of a multicast."""
|
|
|
|
|
|
class MulticastJobError(MulticastError, PluginJobError):
|
|
"""Raise on job error with multicast plugin."""
|
|
|
|
|
|
# DNS
|
|
|
|
|
|
class CoreDNSError(PluginError):
|
|
"""CoreDNS exception."""
|
|
|
|
|
|
class CoreDNSUpdateError(CoreDNSError):
|
|
"""Error on update of a CoreDNS."""
|
|
|
|
|
|
class CoreDNSJobError(CoreDNSError, PluginJobError):
|
|
"""Raise on job error with dns plugin."""
|
|
|
|
|
|
# Audio
|
|
|
|
|
|
class AudioError(PluginError):
|
|
"""PulseAudio exception."""
|
|
|
|
|
|
class AudioUpdateError(AudioError):
|
|
"""Error on update of a Audio."""
|
|
|
|
|
|
class AudioJobError(AudioError, PluginJobError):
|
|
"""Raise on job error with audio plugin."""
|
|
|
|
|
|
# Addons
|
|
|
|
|
|
class AddonsError(HassioError):
|
|
"""Addons exception."""
|
|
|
|
|
|
class AddonConfigurationError(AddonsError):
|
|
"""Error with add-on configuration."""
|
|
|
|
|
|
class AddonConfigurationInvalidError(AddonConfigurationError, APIError):
|
|
"""Raise if invalid configuration provided for addon."""
|
|
|
|
error_key = "addon_configuration_invalid_error"
|
|
message_template = "Add-on {addon} has invalid options: {validation_error}"
|
|
|
|
def __init__(
|
|
self,
|
|
logger: Callable[..., None] | None = None,
|
|
*,
|
|
addon: str,
|
|
validation_error: str,
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"addon": addon, "validation_error": validation_error}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class AddonBootConfigCannotChangeError(AddonsError, APIError):
|
|
"""Raise if user attempts to change addon boot config when it can't be changed."""
|
|
|
|
error_key = "addon_boot_config_cannot_change_error"
|
|
message_template = (
|
|
"Addon {addon} boot option is set to {boot_config} so it cannot be changed"
|
|
)
|
|
|
|
def __init__(
|
|
self, logger: Callable[..., None] | None = None, *, addon: str, boot_config: str
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"addon": addon, "boot_config": boot_config}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class AddonNotRunningError(AddonsError, APIError):
|
|
"""Raise when an addon is not running."""
|
|
|
|
error_key = "addon_not_running_error"
|
|
message_template = "Add-on {addon} is not running"
|
|
|
|
def __init__(
|
|
self, logger: Callable[..., None] | None = None, *, addon: str
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"addon": addon}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class AddonPortConflict(AddonsError, APIError):
|
|
"""Raise if addon cannot start due to a port conflict."""
|
|
|
|
error_key = "addon_port_conflict"
|
|
message_template = "Cannot start addon {name} because port {port} is already in use"
|
|
|
|
def __init__(
|
|
self, logger: Callable[..., None] | None = None, *, name: str, port: int
|
|
) -> None:
|
|
"""Raise & log."""
|
|
self.extra_fields = {"name": name, "port": port}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
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."""
|
|
self.extra_fields = {"slug": slug, "architectures": ", ".join(architectures)}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
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."""
|
|
self.extra_fields = {"slug": slug, "machine_types": ", ".join(machine_types)}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
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."""
|
|
self.extra_fields = {"slug": slug, "version": version}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class AddonNotSupportedWriteStdinError(AddonNotSupportedError, APIError):
|
|
"""Addon does not support writing to stdin."""
|
|
|
|
error_key = "addon_not_supported_write_stdin_error"
|
|
message_template = "Add-on {addon} does not support writing to stdin"
|
|
|
|
def __init__(
|
|
self, logger: Callable[..., None] | None = None, *, addon: str
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"addon": addon}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class AddonBuildDockerfileMissingError(AddonNotSupportedError, APIError):
|
|
"""Raise when addon build invalid because dockerfile is missing."""
|
|
|
|
error_key = "addon_build_dockerfile_missing_error"
|
|
message_template = (
|
|
"Cannot build addon '{addon}' because dockerfile is missing. A repair "
|
|
"using '{repair_command}' will fix this if the cause is data "
|
|
"corruption. Otherwise please report this to the addon developer."
|
|
)
|
|
|
|
def __init__(
|
|
self, logger: Callable[..., None] | None = None, *, addon: str
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"addon": addon, "repair_command": "ha supervisor repair"}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class AddonBuildArchitectureNotSupportedError(AddonNotSupportedError, APIError):
|
|
"""Raise when addon cannot be built on system because it doesn't support its architecture."""
|
|
|
|
error_key = "addon_build_architecture_not_supported_error"
|
|
message_template = (
|
|
"Cannot build addon '{addon}' because its supported architectures "
|
|
"({addon_arches}) do not match the system supported architectures ({system_arches})"
|
|
)
|
|
|
|
def __init__(
|
|
self,
|
|
logger: Callable[..., None] | None = None,
|
|
*,
|
|
addon: str,
|
|
addon_arch_list: list[str],
|
|
system_arch_list: list[str],
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {
|
|
"addon": addon,
|
|
"addon_arches": ", ".join(addon_arch_list),
|
|
"system_arches": ", ".join(system_arch_list),
|
|
}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class AddonUnknownError(AddonsError, APIUnknownSupervisorError):
|
|
"""Raise when unknown error occurs taking an action for an addon."""
|
|
|
|
error_key = "addon_unknown_error"
|
|
message_template = "An unknown error occurred with addon {addon}"
|
|
|
|
def __init__(
|
|
self, logger: Callable[..., None] | None = None, *, addon: str
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"addon": addon}
|
|
super().__init__(logger)
|
|
|
|
|
|
class AddonBuildFailedUnknownError(AddonsError, APIUnknownSupervisorError):
|
|
"""Raise when the build failed for an addon due to an unknown error."""
|
|
|
|
error_key = "addon_build_failed_unknown_error"
|
|
message_template = (
|
|
"An unknown error occurred while trying to build the image for addon {addon}"
|
|
)
|
|
|
|
def __init__(
|
|
self, logger: Callable[..., None] | None = None, *, addon: str
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"addon": addon}
|
|
super().__init__(logger)
|
|
|
|
|
|
class AddonsJobError(AddonsError, JobException):
|
|
"""Raise on job errors."""
|
|
|
|
|
|
# Arch
|
|
|
|
|
|
class HassioArchNotFound(HassioNotSupportedError):
|
|
"""No matches with exists arch."""
|
|
|
|
|
|
# Updater
|
|
|
|
|
|
class UpdaterError(HassioError):
|
|
"""Error on Updater."""
|
|
|
|
|
|
class UpdaterJobError(UpdaterError, JobException):
|
|
"""Raise on job error."""
|
|
|
|
|
|
# Auth
|
|
|
|
|
|
class AuthError(HassioError):
|
|
"""Auth errors."""
|
|
|
|
|
|
class AuthPasswordResetError(AuthError, APIError):
|
|
"""Auth error if password reset failed."""
|
|
|
|
error_key = "auth_password_reset_error"
|
|
message_template = "Username '{user}' does not exist. Check list of users using '{auth_list_command}'."
|
|
|
|
def __init__(
|
|
self,
|
|
logger: Callable[..., None] | None = None,
|
|
*,
|
|
user: str,
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"user": user, "auth_list_command": "ha auth list"}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class AuthListUsersError(AuthError, APIUnknownSupervisorError):
|
|
"""Auth error if listing users failed."""
|
|
|
|
error_key = "auth_list_users_error"
|
|
message_template = "Can't request listing users on Home Assistant"
|
|
|
|
|
|
class AuthInvalidNonStringValueError(AuthError, APIUnauthorized):
|
|
"""Auth error if something besides a string provided as username or password."""
|
|
|
|
error_key = "auth_invalid_non_string_value_error"
|
|
message_template = "Username and password must be strings"
|
|
|
|
def __init__(
|
|
self,
|
|
logger: Callable[..., None] | None = None,
|
|
*,
|
|
headers: Mapping[str, str] | None = None,
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
super().__init__(None, logger, headers=headers)
|
|
|
|
|
|
class AuthHomeAssistantAPIValidationError(AuthError, APIUnknownSupervisorError):
|
|
"""Error encountered trying to validate auth details via Home Assistant API."""
|
|
|
|
error_key = "auth_home_assistant_api_validation_error"
|
|
message_template = "Unable to validate authentication details with Home Assistant"
|
|
|
|
|
|
# Host
|
|
|
|
|
|
class HostError(HassioError):
|
|
"""Internal Host error."""
|
|
|
|
|
|
class HostNotSupportedError(HassioNotSupportedError):
|
|
"""Host function is not supprted."""
|
|
|
|
|
|
class HostServiceError(HostError):
|
|
"""Host service functions failed."""
|
|
|
|
|
|
class HostAppArmorError(HostError):
|
|
"""Host apparmor functions failed."""
|
|
|
|
|
|
class HostNetworkError(HostError):
|
|
"""Error with host network."""
|
|
|
|
|
|
class HostNetworkNotFound(HostError):
|
|
"""Return if host interface is not found."""
|
|
|
|
|
|
class HostLogError(HostError):
|
|
"""Internal error with host log."""
|
|
|
|
|
|
# Service / Discovery
|
|
|
|
|
|
class DiscoveryError(HassioError):
|
|
"""Discovery Errors."""
|
|
|
|
|
|
class ServicesError(HassioError):
|
|
"""Services Errors."""
|
|
|
|
|
|
# utils/dbus
|
|
|
|
|
|
class DBusError(HassioError):
|
|
"""D-Bus generic error."""
|
|
|
|
|
|
class DBusNotConnectedError(HostNotSupportedError):
|
|
"""D-Bus is not connected and call a method."""
|
|
|
|
|
|
class DBusServiceUnkownError(HassioNotSupportedError):
|
|
"""D-Bus service was not available."""
|
|
|
|
|
|
class DBusInterfaceError(HassioNotSupportedError):
|
|
"""D-Bus interface not connected."""
|
|
|
|
|
|
class DBusObjectError(HassioNotSupportedError):
|
|
"""D-Bus object not defined."""
|
|
|
|
|
|
class DBusInterfaceMethodError(DBusInterfaceError):
|
|
"""D-Bus method not defined or input does not match signature."""
|
|
|
|
|
|
class DBusInterfacePropertyError(DBusInterfaceError):
|
|
"""D-Bus property not defined or is read-only."""
|
|
|
|
|
|
class DBusInterfaceSignalError(DBusInterfaceError):
|
|
"""D-Bus signal not defined."""
|
|
|
|
|
|
class DBusParseError(DBusError):
|
|
"""D-Bus parse error."""
|
|
|
|
|
|
class DBusTimeoutError(DBusError):
|
|
"""D-Bus call timeout."""
|
|
|
|
|
|
class DBusTimedOutError(DBusError):
|
|
"""D-Bus call timed out (typically when systemd D-Bus service activation fail)."""
|
|
|
|
|
|
class DBusNoReplyError(DBusError):
|
|
"""D-Bus remote didn't reply/disconnected."""
|
|
|
|
|
|
class DBusFatalError(DBusError):
|
|
"""D-Bus call going wrong.
|
|
|
|
Type field contains specific error from D-Bus for interface specific errors (like Systemd ones).
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str | None = None,
|
|
logger: Callable[..., None] | None = None,
|
|
type_: str | None = None,
|
|
) -> None:
|
|
"""Initialize object."""
|
|
super().__init__(message, logger)
|
|
self.type = type_
|
|
|
|
|
|
# dbus/systemd
|
|
|
|
|
|
class DBusSystemdNoSuchUnit(DBusError):
|
|
"""Systemd unit does not exist."""
|
|
|
|
|
|
# util/apparmor
|
|
|
|
|
|
class AppArmorError(HostAppArmorError):
|
|
"""General AppArmor error."""
|
|
|
|
|
|
class AppArmorFileError(AppArmorError):
|
|
"""AppArmor profile file error."""
|
|
|
|
|
|
class AppArmorInvalidError(AppArmorError):
|
|
"""AppArmor profile validate error."""
|
|
|
|
|
|
# util/boards
|
|
|
|
|
|
class BoardInvalidError(DBusObjectError):
|
|
"""System does not use the board specified."""
|
|
|
|
|
|
# util/common
|
|
|
|
|
|
class ConfigurationFileError(HassioError):
|
|
"""Invalid JSON or YAML file."""
|
|
|
|
|
|
# util/json
|
|
|
|
|
|
class JsonFileError(ConfigurationFileError):
|
|
"""Invalid JSON file."""
|
|
|
|
|
|
# util/yaml
|
|
|
|
|
|
class YamlFileError(ConfigurationFileError):
|
|
"""Invalid YAML file."""
|
|
|
|
|
|
# util/pwned
|
|
|
|
|
|
class PwnedError(HassioError):
|
|
"""Errors while checking pwned passwords."""
|
|
|
|
|
|
class PwnedSecret(PwnedError):
|
|
"""Pwned secrets found."""
|
|
|
|
|
|
class PwnedConnectivityError(PwnedError):
|
|
"""Connectivity errors while checking pwned passwords."""
|
|
|
|
|
|
# util/whoami
|
|
|
|
|
|
class WhoamiError(HassioError):
|
|
"""Error while using whoami."""
|
|
|
|
|
|
class WhoamiSSLError(WhoamiError):
|
|
"""Error with the SSL certificate."""
|
|
|
|
|
|
class WhoamiConnectivityError(WhoamiError):
|
|
"""Connectivity errors while using whoami."""
|
|
|
|
|
|
# utils/systemd_journal
|
|
|
|
|
|
class SystemdJournalError(HassioError):
|
|
"""Error while processing systemd journal logs."""
|
|
|
|
|
|
class MalformedBinaryEntryError(SystemdJournalError):
|
|
"""Raised when binary entry in the journal isn't followed by a newline."""
|
|
|
|
|
|
# docker/api
|
|
|
|
|
|
class DockerError(HassioError):
|
|
"""Docker API/Transport errors."""
|
|
|
|
|
|
class DockerBuildError(DockerError):
|
|
"""Docker error during build."""
|
|
|
|
|
|
class DockerAPIError(DockerError):
|
|
"""Docker API error."""
|
|
|
|
|
|
class DockerTrustError(DockerError):
|
|
"""Raise if images are not trusted."""
|
|
|
|
|
|
class DockerNotFound(DockerError):
|
|
"""Docker object don't Exists."""
|
|
|
|
|
|
class DockerNoSpaceOnDevice(DockerError):
|
|
"""Raise if a docker pull fails due to available space."""
|
|
|
|
error_key = "docker_no_space_on_device"
|
|
message_template = "No space left on disk"
|
|
|
|
def __init__(self, logger: Callable[..., None] | None = None) -> None:
|
|
"""Raise & log."""
|
|
super().__init__(None, logger=logger)
|
|
|
|
|
|
class DockerContainerPortConflict(DockerError, APIError):
|
|
"""Raise if docker cannot start a container due to a port conflict."""
|
|
|
|
error_key = "docker_container_port_conflict"
|
|
message_template = (
|
|
"Cannot start container {name} because port {port} is already in use"
|
|
)
|
|
|
|
def __init__(
|
|
self, logger: Callable[..., None] | None = None, *, name: str, port: int
|
|
) -> None:
|
|
"""Raise & log."""
|
|
self.extra_fields = {"name": name, "port": port}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class DockerRegistryAuthError(DockerError, APIError):
|
|
"""Raise when Docker registry authentication fails."""
|
|
|
|
error_key = "docker_registry_auth_error"
|
|
message_template = (
|
|
"Docker registry authentication failed for {registry}. "
|
|
"Check your registry credentials"
|
|
)
|
|
|
|
def __init__(
|
|
self, logger: Callable[..., None] | None = None, *, registry: str
|
|
) -> None:
|
|
"""Raise & log."""
|
|
self.extra_fields = {"registry": registry}
|
|
super().__init__(None, logger=logger)
|
|
|
|
|
|
class DockerHubRateLimitExceeded(DockerError, APITooManyRequests):
|
|
"""Raise for docker hub rate limit exceeded error."""
|
|
|
|
error_key = "dockerhub_rate_limit_exceeded"
|
|
message_template = (
|
|
"Your IP address has made too many requests to Docker Hub which activated a rate limit. "
|
|
"For more details see {dockerhub_rate_limit_url}"
|
|
)
|
|
extra_fields = {
|
|
"dockerhub_rate_limit_url": "https://www.home-assistant.io/more-info/dockerhub-rate-limit"
|
|
}
|
|
|
|
def __init__(self, logger: Callable[..., None] | None = None) -> None:
|
|
"""Raise & log."""
|
|
super().__init__(None, logger=logger)
|
|
|
|
|
|
class DockerJobError(DockerError, JobException):
|
|
"""Error executing docker job."""
|
|
|
|
|
|
# Hardware
|
|
|
|
|
|
class HardwareError(HassioError):
|
|
"""General Hardware Error on Supervisor."""
|
|
|
|
|
|
class HardwareNotFound(HardwareError):
|
|
"""Hardware path or device doesn't exist on the Host."""
|
|
|
|
|
|
class HardwareNotSupportedError(HassioNotSupportedError):
|
|
"""Raise if hardware function is not supported."""
|
|
|
|
|
|
# Pulse Audio
|
|
|
|
|
|
class PulseAudioError(HassioError):
|
|
"""Raise if an sound error is happening."""
|
|
|
|
|
|
# Resolution
|
|
|
|
|
|
class ResolutionError(HassioError):
|
|
"""Raise if an error is happning on resoltuion."""
|
|
|
|
|
|
class ResolutionCheckError(ResolutionError):
|
|
"""Raise when there are an issue managing checks."""
|
|
|
|
|
|
class ResolutionNotFound(ResolutionError):
|
|
"""Raise if suggestion/issue was not found."""
|
|
|
|
|
|
class ResolutionFixupError(HassioError):
|
|
"""Rasie if a fixup fails."""
|
|
|
|
|
|
class ResolutionFixupJobError(ResolutionFixupError, JobException):
|
|
"""Raise on job error."""
|
|
|
|
|
|
class ResolutionCheckNotFound(ResolutionNotFound, APINotFound): # pylint: disable=too-many-ancestors
|
|
"""Raise if check does not exist."""
|
|
|
|
error_key = "resolution_check_not_found_error"
|
|
message_template = "Check '{check}' does not exist"
|
|
|
|
def __init__(
|
|
self, logger: Callable[..., None] | None = None, *, check: str
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"check": check}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class ResolutionIssueNotFound(ResolutionNotFound, APINotFound): # pylint: disable=too-many-ancestors
|
|
"""Raise if issue does not exist."""
|
|
|
|
error_key = "resolution_issue_not_found_error"
|
|
message_template = "Issue {uuid} does not exist"
|
|
|
|
def __init__(self, logger: Callable[..., None] | None = None, *, uuid: str) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"uuid": uuid}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class ResolutionSuggestionNotFound(ResolutionNotFound, APINotFound): # pylint: disable=too-many-ancestors
|
|
"""Raise if suggestion does not exist."""
|
|
|
|
error_key = "resolution_suggestion_not_found_error"
|
|
message_template = "Suggestion {uuid} does not exist"
|
|
|
|
def __init__(self, logger: Callable[..., None] | None = None, *, uuid: str) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"uuid": uuid}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
# Store
|
|
|
|
|
|
class StoreError(HassioError):
|
|
"""Raise if an error on store is happening."""
|
|
|
|
|
|
class StoreGitError(StoreError):
|
|
"""Raise if something on git is happening."""
|
|
|
|
|
|
class StoreGitCloneError(StoreGitError):
|
|
"""Raise if error occurred while cloning repository."""
|
|
|
|
|
|
class StoreNotFound(StoreError):
|
|
"""Raise if slug is not known."""
|
|
|
|
|
|
class StoreAddonNotFoundError(StoreError, APINotFound):
|
|
"""Raise if a requested addon is not in the store."""
|
|
|
|
error_key = "store_addon_not_found_error"
|
|
message_template = "Addon {addon} does not exist in the store"
|
|
|
|
def __init__(
|
|
self, logger: Callable[..., None] | None = None, *, addon: str
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"addon": addon}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class StoreRepositoryLocalCannotReset(StoreError, APIError):
|
|
"""Raise if user requests a reset on the local addon repository."""
|
|
|
|
error_key = "store_repository_local_cannot_reset"
|
|
message_template = "Can't reset repository {local_repo} as it is not git based!"
|
|
extra_fields = {"local_repo": "local"}
|
|
|
|
def __init__(self, logger: Callable[..., None] | None = None) -> None:
|
|
"""Initialize exception."""
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class StoreJobError(StoreError, JobException):
|
|
"""Raise on job error with git."""
|
|
|
|
|
|
class StoreInvalidAddonRepo(StoreError):
|
|
"""Raise on invalid addon repo."""
|
|
|
|
|
|
class StoreRepositoryUnknownError(StoreError, APIUnknownSupervisorError):
|
|
"""Raise when unknown error occurs taking an action for a store repository."""
|
|
|
|
error_key = "store_repository_unknown_error"
|
|
message_template = "An unknown error occurred with addon repository {repo}"
|
|
|
|
def __init__(self, logger: Callable[..., None] | None = None, *, repo: str) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"repo": repo}
|
|
super().__init__(logger)
|
|
|
|
|
|
# Backup
|
|
|
|
|
|
class BackupError(HassioError):
|
|
"""Raise if an error during backup is happening."""
|
|
|
|
|
|
class HomeAssistantBackupError(BackupError, HomeAssistantError):
|
|
"""Raise if an error during Home Assistant Core backup is happening."""
|
|
|
|
|
|
class BackupInvalidError(BackupError):
|
|
"""Raise if backup or password provided is invalid."""
|
|
|
|
|
|
class BackupMountDownError(BackupError):
|
|
"""Raise if mount specified for backup is down."""
|
|
|
|
|
|
class BackupDataDiskBadMessageError(BackupError):
|
|
"""Raise if bad message error received from data disk during backup."""
|
|
|
|
|
|
class BackupJobError(BackupError, JobException):
|
|
"""Raise on Backup job error."""
|
|
|
|
|
|
class BackupFileNotFoundError(BackupError, APINotFound):
|
|
"""Raise if the backup file hasn't been found."""
|
|
|
|
|
|
class BackupPermissionError(BackupError):
|
|
"""Raise if we could not write the backup due to permission error."""
|
|
|
|
|
|
class BackupFileExistError(BackupError):
|
|
"""Raise if the backup file already exists."""
|
|
|
|
|
|
class AddonBackupMetadataInvalidError(BackupError, APIError):
|
|
"""Raise if invalid metadata file provided for addon in backup."""
|
|
|
|
error_key = "addon_backup_metadata_invalid_error"
|
|
message_template = (
|
|
"Metadata file for add-on {addon} in backup is invalid: {validation_error}"
|
|
)
|
|
|
|
def __init__(
|
|
self,
|
|
logger: Callable[..., None] | None = None,
|
|
*,
|
|
addon: str,
|
|
validation_error: str,
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {"addon": addon, "validation_error": validation_error}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class AddonPrePostBackupCommandReturnedError(BackupError, APIError):
|
|
"""Raise when addon's pre/post backup command returns an error."""
|
|
|
|
error_key = "addon_pre_post_backup_command_returned_error"
|
|
message_template = (
|
|
"Pre-/Post backup command for add-on {addon} returned error code: "
|
|
"{exit_code}. Please report this to the addon developer. Enable debug "
|
|
"logging to capture complete command output using {debug_logging_command}"
|
|
)
|
|
|
|
def __init__(
|
|
self, logger: Callable[..., None] | None = None, *, addon: str, exit_code: int
|
|
) -> None:
|
|
"""Initialize exception."""
|
|
self.extra_fields = {
|
|
"addon": addon,
|
|
"exit_code": exit_code,
|
|
"debug_logging_command": "ha supervisor options --logging debug",
|
|
}
|
|
super().__init__(None, logger)
|
|
|
|
|
|
class BackupRestoreUnknownError(BackupError, APIUnknownSupervisorError):
|
|
"""Raise when an unknown error occurs during backup or restore."""
|
|
|
|
error_key = "backup_restore_unknown_error"
|
|
message_template = "An unknown error occurred during backup/restore"
|
|
|
|
|
|
# Security
|
|
|
|
|
|
class SecurityError(HassioError):
|
|
"""Raise if an error during security checks are happening."""
|
|
|
|
|
|
class SecurityJobError(SecurityError, JobException):
|
|
"""Raise on Security job error."""
|
|
|
|
|
|
# Mount
|
|
|
|
|
|
class MountError(HassioError):
|
|
"""Raise on an error related to mounting/unmounting."""
|
|
|
|
|
|
class MountActivationError(MountError):
|
|
"""Raise on mount not reaching active state after mount/reload."""
|
|
|
|
|
|
class MountInvalidError(MountError):
|
|
"""Raise on invalid mount attempt."""
|
|
|
|
|
|
class MountNotFound(MountError):
|
|
"""Raise on mount not found."""
|
|
|
|
|
|
class MountJobError(MountError, JobException):
|
|
"""Raise on Mount job error."""
|
|
|
|
|
|
# Network
|
|
|
|
|
|
class NetworkInterfaceNotFound(HassioError):
|
|
"""Raise on network interface not found."""
|