Remove customized unknown error types

This commit is contained in:
Mike Degatano
2025-11-18 22:02:56 +00:00
parent c399a3ef27
commit 772d074db9
8 changed files with 180 additions and 344 deletions

View File

@@ -66,30 +66,16 @@ from ..docker.const import ContainerState
from ..docker.monitor import DockerContainerStateEvent
from ..docker.stats import DockerStats
from ..exceptions import (
AddonBackupAppArmorProfileUnknownError,
AddonBackupExportImageUnknownError,
AddonBackupMetadataInvalidError,
AddonBuildImageUnknownError,
AddonConfigurationFileUnknownError,
AddonConfigurationInvalidError,
AddonContainerRunCommandUnknownError,
AddonContainerStartUnknownError,
AddonContainerStatsUnknownError,
AddonContainerStopUnknownError,
AddonContainerWriteStdinUnknownError,
AddonCreateBackupFileUnknownError,
AddonCreateBackupMetadataFileUnknownError,
AddonExtractBackupFileUnknownError,
AddonInstallImageUnknownError,
AddonNotRunningError,
AddonNotSupportedError,
AddonNotSupportedWriteStdinError,
AddonPrePostBackupCommandReturnedError,
AddonRemoveImageUnknownError,
AddonRestoreAppArmorProfileUnknownError,
AddonRestoreBackupDataUnknownError,
AddonsError,
AddonsJobError,
AddonUnknownError,
BackupRestoreUnknownError,
ConfigurationFileError,
DockerBuildError,
DockerError,
@@ -747,7 +733,7 @@ class Addon(AddonModel):
) from None
except ConfigurationFileError as err:
_LOGGER.error("Add-on %s can't write options", self.slug)
raise AddonConfigurationFileUnknownError(addon=self.slug) from err
raise AddonUnknownError(addon=self.slug) from err
_LOGGER.debug("Add-on %s write options: %s", self.slug, options)
@@ -817,11 +803,13 @@ class Addon(AddonModel):
await self.sys_addons.data.uninstall(self)
raise
except DockerBuildError as err:
_LOGGER.error("Could not build image for addon %s: %s", self.slug, err)
await self.sys_addons.data.uninstall(self)
raise AddonBuildImageUnknownError(addon=self.slug) from err
raise AddonUnknownError(addon=self.slug) from err
except DockerError as err:
_LOGGER.error("Could not pull image to update addon %s: %s", self.slug, err)
await self.sys_addons.data.uninstall(self)
raise AddonInstallImageUnknownError(addon=self.slug) from err
raise AddonUnknownError(addon=self.slug) from err
# Finish initialization and set up listeners
await self.load()
@@ -845,7 +833,8 @@ class Addon(AddonModel):
try:
await self.instance.remove(remove_image=remove_image)
except DockerError as err:
raise AddonRemoveImageUnknownError(addon=self.slug) from err
_LOGGER.error("Could not remove image for addon %s: %s", self.slug, err)
raise AddonUnknownError(addon=self.slug) from err
self.state = AddonState.UNKNOWN
@@ -919,9 +908,11 @@ class Addon(AddonModel):
try:
await self.instance.update(store.version, store.image, arch=self.arch)
except DockerBuildError as err:
raise AddonBuildImageUnknownError(addon=self.slug) from err
_LOGGER.error("Could not build image for addon %s: %s", self.slug, err)
raise AddonUnknownError(addon=self.slug) from err
except DockerError as err:
raise AddonInstallImageUnknownError(addon=self.slug) from err
_LOGGER.error("Could not pull image to update addon %s: %s", self.slug, err)
raise AddonUnknownError(addon=self.slug) from err
# Stop the addon if running
if (last_state := self.state) in {AddonState.STARTED, AddonState.STARTUP}:
@@ -967,14 +958,19 @@ class Addon(AddonModel):
try:
await self.instance.remove()
except DockerError as err:
raise AddonRemoveImageUnknownError(addon=self.slug) from err
_LOGGER.error("Could not remove image for addon %s: %s", self.slug, err)
raise AddonUnknownError(addon=self.slug) from err
try:
await self.instance.install(self.version)
except DockerBuildError as err:
raise AddonBuildImageUnknownError(addon=self.slug) from err
_LOGGER.error("Could not build image for addon %s: %s", self.slug, err)
raise AddonUnknownError(addon=self.slug) from err
except DockerError as err:
raise AddonInstallImageUnknownError(addon=self.slug) from err
_LOGGER.error(
"Could not pull image to update addon %s: %s", self.slug, err
)
raise AddonUnknownError(addon=self.slug) from err
if self.addon_store:
await self.sys_addons.data.update(self.addon_store)
@@ -1145,8 +1141,9 @@ class Addon(AddonModel):
try:
await self.instance.run()
except DockerError as err:
_LOGGER.error("Could not start container for addon %s: %s", self.slug, err)
self.state = AddonState.ERROR
raise AddonContainerStartUnknownError(addon=self.slug) from err
raise AddonUnknownError(addon=self.slug) from err
return self.sys_create_task(self._wait_for_startup())
@@ -1161,8 +1158,9 @@ class Addon(AddonModel):
try:
await self.instance.stop()
except DockerError as err:
_LOGGER.error("Could not stop container for addon %s: %s", self.slug, err)
self.state = AddonState.ERROR
raise AddonContainerStopUnknownError(addon=self.slug) from err
raise AddonUnknownError(addon=self.slug) from err
@Job(
name="addon_restart",
@@ -1200,7 +1198,10 @@ class Addon(AddonModel):
return await self.instance.stats()
except DockerError as err:
raise AddonContainerStatsUnknownError(addon=self.slug) from err
_LOGGER.error(
"Could not get stats of container for addon %s: %s", self.slug, err
)
raise AddonUnknownError(addon=self.slug) from err
@Job(
name="addon_write_stdin",
@@ -1218,7 +1219,10 @@ class Addon(AddonModel):
await self.instance.write_stdin(data)
except DockerError as err:
raise AddonContainerWriteStdinUnknownError(addon=self.slug) from err
_LOGGER.error(
"Could not write stdin to container for addon %s: %s", self.slug, err
)
raise AddonUnknownError(addon=self.slug) from err
async def _backup_command(self, command: str) -> None:
try:
@@ -1234,7 +1238,7 @@ class Addon(AddonModel):
_LOGGER.error(
"Failed running pre-/post backup command %s: %s", command, err
)
raise AddonContainerRunCommandUnknownError(addon=self.slug) from err
raise AddonUnknownError(addon=self.slug) from err
@Job(
name="addon_begin_backup",
@@ -1323,18 +1327,14 @@ class Addon(AddonModel):
try:
self.instance.export_image(temp_path.joinpath("image.tar"))
except DockerError as err:
raise AddonBackupExportImageUnknownError(
addon=self.slug
) from err
raise BackupRestoreUnknownError() from err
# Store local configs/state
try:
write_json_file(temp_path.joinpath("addon.json"), metadata)
except ConfigurationFileError as err:
_LOGGER.error("Can't save meta for %s: %s", self.slug, err)
raise AddonCreateBackupMetadataFileUnknownError(
addon=self.slug
) from err
raise BackupRestoreUnknownError() from err
# Store AppArmor Profile
if apparmor_profile:
@@ -1344,9 +1344,7 @@ class Addon(AddonModel):
apparmor_profile, profile_backup_file
)
except HostAppArmorError as err:
raise AddonBackupAppArmorProfileUnknownError(
addon=self.slug
) from err
raise BackupRestoreUnknownError() from err
# Write tarfile
with tar_file as backup:
@@ -1401,7 +1399,7 @@ class Addon(AddonModel):
_LOGGER.info("Finish backup for addon %s", self.slug)
except (tarfile.TarError, OSError, AddFileError) as err:
_LOGGER.error("Can't write backup tarfile for addon %s: %s", self.slug, err)
raise AddonCreateBackupFileUnknownError(addon=self.slug) from err
raise BackupRestoreUnknownError() from err
finally:
if was_running:
wait_for_start = await self.end_backup()
@@ -1444,9 +1442,9 @@ class Addon(AddonModel):
tmp, data = await self.sys_run_in_executor(_extract_tarfile)
except tarfile.TarError as err:
_LOGGER.error("Can't extract backup tarfile for %s: %s", self.slug, err)
raise AddonExtractBackupFileUnknownError(addon=self.slug) from err
raise BackupRestoreUnknownError() from err
except ConfigurationFileError as err:
raise AddonConfigurationFileUnknownError(addon=self.slug) from err
raise AddonUnknownError(addon=self.slug) from err
try:
# Validate
@@ -1522,7 +1520,7 @@ class Addon(AddonModel):
_LOGGER.error(
"Can't restore origin data for %s: %s", self.slug, err
)
raise AddonRestoreBackupDataUnknownError(addon=self.slug) from err
raise BackupRestoreUnknownError() from err
# Restore AppArmor
profile_file = Path(tmp.name, "apparmor.txt")
@@ -1537,9 +1535,7 @@ class Addon(AddonModel):
self.slug,
err,
)
raise AddonRestoreAppArmorProfileUnknownError(
addon=self.slug
) from err
raise BackupRestoreUnknownError() from err
finally:
# Is add-on loaded

View File

@@ -628,9 +628,6 @@ class Backup(JobGroup):
if start_task := await self._addon_save(addon):
start_tasks.append(start_task)
except BackupError as err:
err = BackupError(
f"Can't backup add-on {addon.slug}: {str(err)}", _LOGGER.error
)
self.sys_jobs.current.capture_error(err)
return start_tasks

View File

@@ -358,26 +358,6 @@ class AddonConfigurationInvalidError(AddonConfigurationError, APIError):
super().__init__(None, logger)
class AddonBackupMetadataInvalidError(AddonsError, 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 AddonBootConfigCannotChangeError(AddonsError, APIError):
"""Raise if user attempts to change addon boot config when it can't be changed."""
@@ -408,28 +388,6 @@ class AddonNotRunningError(AddonsError, APIError):
super().__init__(None, logger)
class AddonPrePostBackupCommandReturnedError(AddonsError, 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 AddonNotSupportedError(HassioNotSupportedError):
"""Addon doesn't support a function."""
@@ -546,238 +504,11 @@ class AddonBuildArchitectureNotSupportedError(AddonNotSupportedError, APIError):
super().__init__(None, logger)
# pylint: disable-next=too-many-ancestors
class AddonConfigurationFileUnknownError(
AddonConfigurationError, APIUnknownSupervisorError
):
"""Raise when unknown error occurs trying to read/write addon configuration file."""
class AddonUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when unknown error occurs taking an action for an addon."""
error_key = "addon_configuration_file_unknown_error"
message_template = (
"An unknown error occurred reading/writing configuration file for {addon}"
)
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonBuildImageUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs during image build."""
error_key = "addon_build_image_unknown_error"
message_template = "An unknown error occurred during build of image for {addon}"
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonInstallImageUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs during image install."""
error_key = "addon_install_image_unknown_error"
message_template = "An unknown error occurred during install of image for {addon}"
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonRemoveImageUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs while removing an image."""
error_key = "addon_remove_image_unknown_error"
message_template = "An unknown error occurred while removing image for {addon}"
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonContainerStartUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs while starting a container."""
error_key = "addon_container_start_unknown_error"
message_template = "An unknown error occurred while starting container for {addon}"
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonContainerStopUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs while stopping a container."""
error_key = "addon_container_stop_unknown_error"
message_template = "An unknown error occurred while stopping container for {addon}"
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonContainerStatsUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs while getting stats of a container."""
error_key = "addon_container_stats_unknown_error"
message_template = (
"An unknown error occurred while getting stats of container for {addon}"
)
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonContainerWriteStdinUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs while writing to stdin of a container."""
error_key = "addon_container_write_stdin_unknown_error"
message_template = (
"An unknown error occurred while writing to stdin of container for {addon}"
)
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonContainerRunCommandUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs while running command inside of a container."""
error_key = "addon_container_run_command_unknown_error"
message_template = "An unknown error occurred while running a command inside of container for {addon}"
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonCreateBackupFileUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs while making the backup file for an addon."""
error_key = "addon_create_backup_file_unknown_error"
message_template = (
"An unknown error occurred while creating the backup file for {addon}"
)
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonCreateBackupMetadataFileUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs while making the metadata file for an addon backup."""
error_key = "addon_create_backup_metadata_file_unknown_error"
message_template = "An unknown error occurred while creating the metadata file for backup of {addon}"
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonBackupAppArmorProfileUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs while backing up the AppArmor profile of an addon."""
error_key = "addon_backup_app_armor_profile_unknown_error"
message_template = (
"An unknown error occurred while backing up the AppArmor profile of {addon}"
)
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonBackupExportImageUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs while exporting image for an addon backup."""
error_key = "addon_backup_export_image_unknown_error"
message_template = (
"An unknown error occurred while exporting image to back up {addon}"
)
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonExtractBackupFileUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when an unknown error occurs while extracting backup file for an addon."""
error_key = "addon_extract_backup_file_unknown_error"
message_template = (
"An unknown error occurred while extracting the backup file for {addon}"
)
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonRestoreBackupDataUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when unknown error occurs while restoring data/config for addon from backup."""
error_key = "addon_restore_backup_data_unknown_error"
message_template = "An unknown error occurred while restoring data and config for {addon} from backup"
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
) -> None:
"""Initialize exception."""
self.extra_fields = {"addon": addon}
super().__init__(logger)
class AddonRestoreAppArmorProfileUnknownError(AddonsError, APIUnknownSupervisorError):
"""Raise when unknown error occurs while restoring AppArmor profile for addon from backup."""
error_key = "addon_restore_app_armor_profile_unknown_error"
message_template = "An unknown error occurred while restoring AppArmor profile for {addon} from backup"
error_key = "addon_unknown_error"
message_template = "An unknown error occurred with addon {addon}"
def __init__(
self, logger: Callable[..., None] | None = None, *, addon: str
@@ -1262,6 +993,55 @@ 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

View File

@@ -102,13 +102,17 @@ class SupervisorJobError:
"Unknown error, see Supervisor logs (check with 'ha supervisor logs')"
)
stage: str | None = None
error_key: str | None = None
extra_fields: dict[str, Any] | None = None
def as_dict(self) -> dict[str, str | None]:
def as_dict(self) -> dict[str, Any]:
"""Return dictionary representation."""
return {
"type": self.type_.__name__,
"message": self.message,
"stage": self.stage,
"error_key": self.error_key,
"extra_fields": self.extra_fields,
}
@@ -158,7 +162,9 @@ class SupervisorJob:
def capture_error(self, err: HassioError | None = None) -> None:
"""Capture an error or record that an unknown error has occurred."""
if err:
new_error = SupervisorJobError(type(err), str(err), self.stage)
new_error = SupervisorJobError(
type(err), str(err), self.stage, err.error_key, err.extra_fields
)
else:
new_error = SupervisorJobError(stage=self.stage)
self.errors += [new_error]

View File

@@ -5,6 +5,7 @@ from datetime import timedelta
import errno
from http import HTTPStatus
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, PropertyMock, call, patch
import aiodocker
@@ -23,7 +24,13 @@ from supervisor.docker.addon import DockerAddon
from supervisor.docker.const import ContainerState
from supervisor.docker.manager import CommandReturn, DockerAPI
from supervisor.docker.monitor import DockerContainerStateEvent
from supervisor.exceptions import AddonsError, AddonsJobError, AudioUpdateError
from supervisor.exceptions import (
AddonPrePostBackupCommandReturnedError,
AddonsJobError,
AddonUnknownError,
AudioUpdateError,
HassioError,
)
from supervisor.hardware.helper import HwHelper
from supervisor.ingress import Ingress
from supervisor.store.repository import Repository
@@ -502,31 +509,26 @@ async def test_backup_with_pre_post_command(
@pytest.mark.parametrize(
"get_error,exception_on_exec",
("container_get_side_effect", "exec_run_side_effect", "exc_type_raised"),
[
(NotFound("missing"), False),
(DockerException(), False),
(None, True),
(None, False),
(NotFound("missing"), [(1, None)], AddonUnknownError),
(DockerException(), [(1, None)], AddonUnknownError),
(None, DockerException(), AddonUnknownError),
(None, [(1, None)], AddonPrePostBackupCommandReturnedError),
],
)
@pytest.mark.usefixtures("tmp_supervisor_data", "path_extern")
async def test_backup_with_pre_command_error(
coresys: CoreSys,
install_addon_ssh: Addon,
container: MagicMock,
get_error: DockerException | None,
exception_on_exec: bool,
tmp_supervisor_data,
path_extern,
container_get_side_effect: DockerException | None,
exec_run_side_effect: DockerException | list[tuple[int, Any]],
exc_type_raised: type[HassioError],
) -> None:
"""Test backing up an addon with error running pre command."""
if get_error:
coresys.docker.containers.get.side_effect = get_error
if exception_on_exec:
container.exec_run.side_effect = DockerException()
else:
container.exec_run.return_value = (1, None)
coresys.docker.containers.get.side_effect = container_get_side_effect
container.exec_run.side_effect = exec_run_side_effect
install_addon_ssh.path_data.mkdir()
await install_addon_ssh.load()
@@ -535,7 +537,7 @@ async def test_backup_with_pre_command_error(
with (
patch.object(DockerAddon, "is_running", return_value=True),
patch.object(Addon, "backup_pre", new=PropertyMock(return_value="backup_pre")),
pytest.raises(AddonsError),
pytest.raises(exc_type_raised),
):
assert await install_addon_ssh.backup(tarfile) is None

View File

@@ -590,9 +590,9 @@ async def test_addon_start_options_error(
body = await resp.json()
assert (
body["message"]
== "An unknown error occurred reading/writing configuration file for local_example. Check supervisor logs for details (check with 'ha supervisor logs')"
== "An unknown error occurred with addon local_example. Check supervisor logs for details (check with 'ha supervisor logs')"
)
assert body["error_key"] == "addon_configuration_file_unknown_error"
assert body["error_key"] == "addon_unknown_error"
assert body["extra_fields"] == {
"addon": "local_example",
"logs_command": "ha supervisor logs",

View File

@@ -17,6 +17,7 @@ from supervisor.const import CoreState
from supervisor.coresys import CoreSys
from supervisor.docker.manager import DockerAPI
from supervisor.exceptions import (
AddonPrePostBackupCommandReturnedError,
AddonsError,
BackupInvalidError,
HomeAssistantBackupError,
@@ -24,6 +25,7 @@ from supervisor.exceptions import (
from supervisor.homeassistant.core import HomeAssistantCore
from supervisor.homeassistant.module import HomeAssistant
from supervisor.homeassistant.websocket import HomeAssistantWebSocket
from supervisor.jobs import SupervisorJob
from supervisor.mounts.mount import Mount
from supervisor.supervisor import Supervisor
@@ -401,6 +403,8 @@ async def test_api_backup_errors(
"type": "BackupError",
"message": str(err),
"stage": None,
"error_key": None,
"extra_fields": None,
}
]
assert job["child_jobs"][2]["name"] == "backup_store_folders"
@@ -437,6 +441,8 @@ async def test_api_backup_errors(
"type": "HomeAssistantBackupError",
"message": "Backup error",
"stage": "home_assistant",
"error_key": None,
"extra_fields": None,
}
]
assert job["child_jobs"][0]["name"] == "backup_store_homeassistant"
@@ -445,6 +451,8 @@ async def test_api_backup_errors(
"type": "HomeAssistantBackupError",
"message": "Backup error",
"stage": None,
"error_key": None,
"extra_fields": None,
}
]
assert len(job["child_jobs"]) == 1
@@ -749,6 +757,8 @@ async def test_backup_to_multiple_locations_error_on_copy(
"type": "BackupError",
"message": "Could not copy backup to .cloud_backup due to: ",
"stage": None,
"error_key": None,
"extra_fields": None,
}
]
@@ -1483,3 +1493,44 @@ async def test_immediate_list_after_missing_file_restore(
result = await resp.json()
assert len(result["data"]["backups"]) == 1
assert result["data"]["backups"][0]["slug"] == "93b462f8"
@pytest.mark.parametrize("command", ["backup_pre", "backup_post"])
@pytest.mark.usefixtures("install_addon_example", "tmp_supervisor_data")
async def test_pre_post_backup_command_error(
api_client: TestClient, coresys: CoreSys, container: MagicMock, command: str
):
"""Test pre/post backup command error."""
await coresys.core.set_state(CoreState.RUNNING)
coresys.hardware.disk.get_disk_free_space = lambda x: 5000
container.status = "running"
container.exec_run.return_value = (1, b"")
with patch.object(Addon, command, return_value=PropertyMock(return_value="test")):
resp = await api_client.post(
"/backups/new/partial", json={"addons": ["local_example"]}
)
assert resp.status == 200
body = await resp.json()
job_id = body["data"]["job_id"]
job: SupervisorJob | None = None
for j in coresys.jobs.jobs:
if j.name == "backup_store_addons" and j.parent_id == job_id:
job = j
break
assert job
assert job.done is True
assert job.errors[0].type_ == AddonPrePostBackupCommandReturnedError
assert job.errors[0].message == (
"Pre-/Post backup command for add-on local_example returned error code: "
"1. Please report this to the addon developer. Enable debug "
"logging to capture complete command output using ha supervisor options --logging debug"
)
assert job.errors[0].error_key == "addon_pre_post_backup_command_returned_error"
assert job.errors[0].extra_fields == {
"addon": "local_example",
"exit_code": 1,
"debug_logging_command": "ha supervisor options --logging debug",
}

View File

@@ -374,6 +374,8 @@ async def test_job_with_error(
"type": "SupervisorError",
"message": "bad",
"stage": "test",
"error_key": None,
"extra_fields": None,
}
],
"child_jobs": [
@@ -391,6 +393,8 @@ async def test_job_with_error(
"type": "SupervisorError",
"message": "bad",
"stage": None,
"error_key": None,
"extra_fields": None,
}
],
"child_jobs": [],