mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-24 09:36:31 +00:00
commit
76ad6dca02
@ -155,7 +155,7 @@ class AddonManager(CoreSysAttributes):
|
||||
await addon.instance.install(store.version, store.image)
|
||||
except DockerAPIError:
|
||||
self.data.uninstall(addon)
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
else:
|
||||
self.local[slug] = addon
|
||||
|
||||
@ -175,7 +175,7 @@ class AddonManager(CoreSysAttributes):
|
||||
try:
|
||||
await addon.instance.remove()
|
||||
except DockerAPIError:
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
await addon.remove_data()
|
||||
|
||||
@ -246,7 +246,7 @@ class AddonManager(CoreSysAttributes):
|
||||
with suppress(DockerAPIError):
|
||||
await addon.instance.cleanup()
|
||||
except DockerAPIError:
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
else:
|
||||
self.data.update(store)
|
||||
_LOGGER.info("Add-on '%s' successfully updated", slug)
|
||||
@ -284,7 +284,7 @@ class AddonManager(CoreSysAttributes):
|
||||
await addon.instance.remove()
|
||||
await addon.instance.install(addon.version)
|
||||
except DockerAPIError:
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
else:
|
||||
self.data.update(store)
|
||||
_LOGGER.info("Add-on '%s' successfully rebuilt", slug)
|
||||
|
@ -489,14 +489,14 @@ class Addon(AddonModel):
|
||||
try:
|
||||
await self.instance.run()
|
||||
except DockerAPIError:
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""Stop add-on."""
|
||||
try:
|
||||
return await self.instance.stop()
|
||||
except DockerAPIError:
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
async def restart(self) -> None:
|
||||
"""Restart add-on."""
|
||||
@ -516,7 +516,7 @@ class Addon(AddonModel):
|
||||
try:
|
||||
return await self.instance.stats()
|
||||
except DockerAPIError:
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
async def write_stdin(self, data) -> None:
|
||||
"""Write data to add-on stdin.
|
||||
@ -530,7 +530,7 @@ class Addon(AddonModel):
|
||||
try:
|
||||
return await self.instance.write_stdin(data)
|
||||
except DockerAPIError:
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
async def snapshot(self, tar_file: tarfile.TarFile) -> None:
|
||||
"""Snapshot state of an add-on."""
|
||||
@ -542,7 +542,7 @@ class Addon(AddonModel):
|
||||
try:
|
||||
await self.instance.export_image(temp_path.joinpath("image.tar"))
|
||||
except DockerAPIError:
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
data = {
|
||||
ATTR_USER: self.persist,
|
||||
@ -556,7 +556,7 @@ class Addon(AddonModel):
|
||||
write_json_file(temp_path.joinpath("addon.json"), data)
|
||||
except JsonFileError:
|
||||
_LOGGER.error("Can't save meta for %s", self.slug)
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
# Store AppArmor Profile
|
||||
if self.sys_host.apparmor.exists(self.slug):
|
||||
@ -565,7 +565,7 @@ class Addon(AddonModel):
|
||||
self.sys_host.apparmor.backup_profile(self.slug, profile)
|
||||
except HostAppArmorError:
|
||||
_LOGGER.error("Can't backup AppArmor profile")
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
# write into tarfile
|
||||
def _write_tarfile():
|
||||
@ -588,7 +588,7 @@ class Addon(AddonModel):
|
||||
await self.sys_run_in_executor(_write_tarfile)
|
||||
except (tarfile.TarError, OSError) as err:
|
||||
_LOGGER.error("Can't write tarfile %s: %s", tar_file, err)
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
_LOGGER.info("Finish snapshot for addon %s", self.slug)
|
||||
|
||||
@ -605,13 +605,13 @@ class Addon(AddonModel):
|
||||
await self.sys_run_in_executor(_extract_tarfile)
|
||||
except tarfile.TarError as err:
|
||||
_LOGGER.error("Can't read tarfile %s: %s", tar_file, err)
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
# Read snapshot data
|
||||
try:
|
||||
data = read_json_file(Path(temp, "addon.json"))
|
||||
except JsonFileError:
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
# Validate
|
||||
try:
|
||||
@ -622,7 +622,7 @@ class Addon(AddonModel):
|
||||
self.slug,
|
||||
humanize_error(data, err),
|
||||
)
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
# If available
|
||||
if not self._available(data[ATTR_SYSTEM]):
|
||||
@ -669,7 +669,7 @@ class Addon(AddonModel):
|
||||
await self.sys_run_in_executor(_restore_data)
|
||||
except shutil.Error as err:
|
||||
_LOGGER.error("Can't restore origin data: %s", err)
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
# Restore AppArmor
|
||||
profile_file = Path(temp, "apparmor.txt")
|
||||
@ -680,7 +680,7 @@ class Addon(AddonModel):
|
||||
_LOGGER.error(
|
||||
"Can't restore AppArmor profile for add-on %s", self.slug
|
||||
)
|
||||
raise AddonsError() from None
|
||||
raise AddonsError()
|
||||
|
||||
# Run add-on
|
||||
if data[ATTR_STATE] == STATE_STARTED:
|
||||
|
@ -362,7 +362,7 @@ def validate_options(coresys: CoreSys, raw_schema: Dict[str, Any]):
|
||||
# normal value
|
||||
options[key] = _single_validate(coresys, typ, value, key)
|
||||
except (IndexError, KeyError):
|
||||
raise vol.Invalid(f"Type error for {key}") from None
|
||||
raise vol.Invalid(f"Type error for {key}")
|
||||
|
||||
_check_missing_options(raw_schema, options, "root")
|
||||
return options
|
||||
|
@ -104,7 +104,7 @@ class APIIngress(CoreSysAttributes):
|
||||
except aiohttp.ClientError as err:
|
||||
_LOGGER.error("Ingress error: %s", err)
|
||||
|
||||
raise HTTPBadGateway() from None
|
||||
raise HTTPBadGateway()
|
||||
|
||||
async def _handle_websocket(
|
||||
self, request: web.Request, addon: Addon, path: str
|
||||
|
@ -148,7 +148,7 @@ class APIProxy(CoreSysAttributes):
|
||||
|
||||
raise HomeAssistantAuthError()
|
||||
|
||||
except (RuntimeError, ValueError, ClientConnectorError) as err:
|
||||
except (RuntimeError, ValueError, TypeError, ClientConnectorError) as err:
|
||||
_LOGGER.error("Client error on WebSocket API %s.", err)
|
||||
except HomeAssistantAuthError:
|
||||
_LOGGER.error("Failed authentication to Home Assistant WebSocket")
|
||||
|
@ -120,7 +120,7 @@ async def api_validate(
|
||||
try:
|
||||
data_validated = schema(data)
|
||||
except vol.Invalid as ex:
|
||||
raise APIError(humanize_error(data, ex)) from None
|
||||
raise APIError(humanize_error(data, ex))
|
||||
|
||||
if not origin:
|
||||
return data_validated
|
||||
|
@ -281,11 +281,6 @@ def supervisor_debugger(coresys: CoreSys) -> None:
|
||||
|
||||
def setup_diagnostics(coresys: CoreSys) -> None:
|
||||
"""Sentry diagnostic backend."""
|
||||
|
||||
def filter_event(event, hint):
|
||||
return filter_data(coresys, event, hint)
|
||||
|
||||
# Set log level
|
||||
sentry_logging = LoggingIntegration(
|
||||
level=logging.WARNING, event_level=logging.CRITICAL
|
||||
)
|
||||
@ -293,7 +288,7 @@ def setup_diagnostics(coresys: CoreSys) -> None:
|
||||
_LOGGER.info("Initialize Supervisor Sentry")
|
||||
sentry_sdk.init(
|
||||
dsn="https://9c6ea70f49234442b4746e447b24747e@o427061.ingest.sentry.io/5370612",
|
||||
before_send=filter_event,
|
||||
before_send=lambda event, hint: filter_data(coresys, event, hint),
|
||||
max_breadcrumbs=30,
|
||||
integrations=[AioHttpIntegration(), sentry_logging],
|
||||
release=SUPERVISOR_VERSION,
|
||||
|
@ -3,7 +3,7 @@ from enum import Enum
|
||||
from ipaddress import ip_network
|
||||
from pathlib import Path
|
||||
|
||||
SUPERVISOR_VERSION = "233"
|
||||
SUPERVISOR_VERSION = "234"
|
||||
|
||||
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
|
||||
URL_HASSIO_VERSION = "https://version.home-assistant.io/{channel}.json"
|
||||
|
@ -233,19 +233,25 @@ class Core(CoreSysAttributes):
|
||||
|
||||
async def stop(self):
|
||||
"""Stop a running orchestration."""
|
||||
# don't process scheduler anymore
|
||||
self.state = CoreStates.STOPPING
|
||||
|
||||
# store new last boot / prevent time adjustments
|
||||
if self.state == CoreStates.RUNNING:
|
||||
self._update_last_boot()
|
||||
|
||||
# process async stop tasks
|
||||
# don't process scheduler anymore
|
||||
self.state = CoreStates.STOPPING
|
||||
|
||||
# Stage 1
|
||||
try:
|
||||
with async_timeout.timeout(10):
|
||||
async with async_timeout.timeout(10):
|
||||
await asyncio.wait([self.sys_api.stop()])
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.warning("Stage 1: Force Shutdown!")
|
||||
|
||||
# Stage 2
|
||||
try:
|
||||
async with async_timeout.timeout(10):
|
||||
await asyncio.wait(
|
||||
[
|
||||
self.sys_api.stop(),
|
||||
self.sys_websession.close(),
|
||||
self.sys_websession_ssl.close(),
|
||||
self.sys_ingress.unload(),
|
||||
@ -254,7 +260,7 @@ class Core(CoreSysAttributes):
|
||||
]
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.warning("Force Shutdown!")
|
||||
_LOGGER.warning("Stage 2: Force Shutdown!")
|
||||
|
||||
_LOGGER.info("Supervisor is down")
|
||||
|
||||
|
@ -79,7 +79,7 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
||||
config = valid_discovery_config(service, config)
|
||||
except vol.Invalid as err:
|
||||
_LOGGER.error("Invalid discovery %s config", humanize_error(config, err))
|
||||
raise DiscoveryError() from None
|
||||
raise DiscoveryError()
|
||||
|
||||
# Create message
|
||||
message = Message(addon.slug, service, config)
|
||||
|
@ -130,7 +130,7 @@ class DockerAPI:
|
||||
)
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.error("Can't create container from %s: %s", name, err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
# Attach network
|
||||
if not network_mode:
|
||||
@ -148,7 +148,7 @@ class DockerAPI:
|
||||
container.start()
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.error("Can't start %s: %s", name, err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
# Update metadata
|
||||
with suppress(docker.errors.DockerException):
|
||||
@ -186,7 +186,7 @@ class DockerAPI:
|
||||
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.error("Can't execute command: %s", err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
finally:
|
||||
# cleanup container
|
||||
|
@ -398,7 +398,7 @@ class DockerAddon(DockerInterface):
|
||||
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.error("Can't build %s:%s: %s", self.image, tag, err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
_LOGGER.info("Build %s:%s done", self.image, tag)
|
||||
|
||||
@ -416,7 +416,7 @@ class DockerAddon(DockerInterface):
|
||||
image = self.sys_docker.api.get_image(f"{self.image}:{self.version}")
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.error("Can't fetch image %s: %s", self.image, err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
_LOGGER.info("Export image %s to %s", self.image, tar_file)
|
||||
try:
|
||||
@ -425,7 +425,7 @@ class DockerAddon(DockerInterface):
|
||||
write_tar.write(chunk)
|
||||
except (OSError, requests.exceptions.ReadTimeout) as err:
|
||||
_LOGGER.error("Can't write tar file %s: %s", tar_file, err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
_LOGGER.info("Export image %s done", self.image)
|
||||
|
||||
@ -446,7 +446,7 @@ class DockerAddon(DockerInterface):
|
||||
docker_image = self.sys_docker.images.get(f"{self.image}:{self.version}")
|
||||
except (docker.errors.DockerException, OSError) as err:
|
||||
_LOGGER.error("Can't import image %s: %s", self.image, err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
self._meta = docker_image.attrs
|
||||
_LOGGER.info("Import image %s and version %s", tar_file, self.version)
|
||||
@ -465,7 +465,7 @@ class DockerAddon(DockerInterface):
|
||||
Need run inside executor.
|
||||
"""
|
||||
if not self._is_running():
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
try:
|
||||
# Load needed docker objects
|
||||
@ -473,7 +473,7 @@ class DockerAddon(DockerInterface):
|
||||
socket = container.attach_socket(params={"stdin": 1, "stream": 1})
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.error("Can't attach to %s stdin: %s", self.name, err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
try:
|
||||
# Write to stdin
|
||||
@ -482,7 +482,7 @@ class DockerAddon(DockerInterface):
|
||||
socket.close()
|
||||
except OSError as err:
|
||||
_LOGGER.error("Can't write to %s stdin: %s", self.name, err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
def _stop(self, remove_container=True) -> None:
|
||||
"""Stop/remove Docker container.
|
||||
|
@ -106,7 +106,7 @@ class DockerInterface(CoreSysAttributes):
|
||||
"Available space in /data is: %s GiB",
|
||||
free_space,
|
||||
)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
else:
|
||||
self._meta = docker_image.attrs
|
||||
|
||||
@ -162,7 +162,7 @@ class DockerInterface(CoreSysAttributes):
|
||||
|
||||
# Successfull?
|
||||
if not self._meta:
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
_LOGGER.info("Attach to %s with version %s", self.image, self.version)
|
||||
|
||||
@process_lock
|
||||
@ -190,7 +190,7 @@ class DockerInterface(CoreSysAttributes):
|
||||
try:
|
||||
docker_container = self.sys_docker.containers.get(self.name)
|
||||
except docker.errors.DockerException:
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
if docker_container.status == "running":
|
||||
_LOGGER.info("Stop %s application", self.name)
|
||||
@ -215,14 +215,14 @@ class DockerInterface(CoreSysAttributes):
|
||||
try:
|
||||
docker_container = self.sys_docker.containers.get(self.name)
|
||||
except docker.errors.DockerException:
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
_LOGGER.info("Start %s", self.name)
|
||||
try:
|
||||
docker_container.start()
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.error("Can't start %s: %s", self.name, err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
@process_lock
|
||||
def remove(self) -> Awaitable[None]:
|
||||
@ -251,7 +251,7 @@ class DockerInterface(CoreSysAttributes):
|
||||
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.warning("Can't remove image %s: %s", self.image, err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
self._meta = None
|
||||
|
||||
@ -320,7 +320,7 @@ class DockerInterface(CoreSysAttributes):
|
||||
origin = self.sys_docker.images.get(f"{self.image}:{self.version}")
|
||||
except docker.errors.DockerException:
|
||||
_LOGGER.warning("Can't find %s for cleanup", self.image)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
# Cleanup Current
|
||||
for image in self.sys_docker.images.list(name=self.image):
|
||||
@ -353,14 +353,14 @@ class DockerInterface(CoreSysAttributes):
|
||||
try:
|
||||
container = self.sys_docker.containers.get(self.name)
|
||||
except docker.errors.DockerException:
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
_LOGGER.info("Restart %s", self.image)
|
||||
try:
|
||||
container.restart(timeout=self.timeout)
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.warning("Can't restart %s: %s", self.image, err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
@process_lock
|
||||
def execute_command(self, command: str) -> Awaitable[CommandReturn]:
|
||||
@ -386,14 +386,14 @@ class DockerInterface(CoreSysAttributes):
|
||||
try:
|
||||
docker_container = self.sys_docker.containers.get(self.name)
|
||||
except docker.errors.DockerException:
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
try:
|
||||
stats = docker_container.stats(stream=False)
|
||||
return DockerStats(stats)
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.error("Can't read stats from %s: %s", self.name, err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
def is_fails(self) -> Awaitable[bool]:
|
||||
"""Return True if Docker is failing state.
|
||||
|
@ -105,7 +105,7 @@ class DockerNetwork:
|
||||
self.network.connect(container, aliases=alias, ipv4_address=ipv4_address)
|
||||
except docker.errors.APIError as err:
|
||||
_LOGGER.error("Can't link container to hassio-net: %s", err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
self.network.reload()
|
||||
|
||||
@ -125,7 +125,7 @@ class DockerNetwork:
|
||||
|
||||
except docker.errors.APIError as err:
|
||||
_LOGGER.warning("Can't disconnect container from default: %s", err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
def stale_cleanup(self, container_name: str):
|
||||
"""Remove force a container from Network.
|
||||
|
@ -39,7 +39,7 @@ class DockerSupervisor(DockerInterface, CoreSysAttributes):
|
||||
try:
|
||||
docker_container = self.sys_docker.containers.get(self.name)
|
||||
except docker.errors.DockerException:
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
self._meta = docker_container.attrs
|
||||
_LOGGER.info(
|
||||
@ -76,7 +76,7 @@ class DockerSupervisor(DockerInterface, CoreSysAttributes):
|
||||
docker_container.image.tag(self.image, tag="latest")
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.error("Can't retag supervisor version: %s", err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
||||
def update_start_tag(self, image: str, version: str) -> Awaitable[None]:
|
||||
"""Update start tag to new version."""
|
||||
@ -103,4 +103,4 @@ class DockerSupervisor(DockerInterface, CoreSysAttributes):
|
||||
|
||||
except docker.errors.DockerException as err:
|
||||
_LOGGER.error("Can't fix start tag: %s", err)
|
||||
raise DockerAPIError() from None
|
||||
raise DockerAPIError()
|
||||
|
@ -142,7 +142,7 @@ class HassOS(CoreSysAttributes):
|
||||
|
||||
except DBusError:
|
||||
_LOGGER.error("Rauc communication error")
|
||||
raise HassOSUpdateError() from None
|
||||
raise HassOSUpdateError()
|
||||
|
||||
finally:
|
||||
int_ota.unlink()
|
||||
|
@ -344,7 +344,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
)
|
||||
except DockerAPIError:
|
||||
_LOGGER.warning("Update Home Assistant image fails")
|
||||
raise HomeAssistantUpdateError() from None
|
||||
raise HomeAssistantUpdateError()
|
||||
else:
|
||||
self.version = self.instance.version
|
||||
self.image = self.sys_updater.image_homeassistant
|
||||
@ -393,7 +393,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
try:
|
||||
await self.instance.run()
|
||||
except DockerAPIError:
|
||||
raise HomeAssistantError() from None
|
||||
raise HomeAssistantError()
|
||||
|
||||
await self._block_till_run(self.version)
|
||||
|
||||
@ -409,7 +409,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
try:
|
||||
await self.instance.start()
|
||||
except DockerAPIError:
|
||||
raise HomeAssistantError() from None
|
||||
raise HomeAssistantError()
|
||||
|
||||
await self._block_till_run(self.version)
|
||||
# No Instance/Container found, extended start
|
||||
@ -425,7 +425,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
try:
|
||||
return await self.instance.stop(remove_container=False)
|
||||
except DockerAPIError:
|
||||
raise HomeAssistantError() from None
|
||||
raise HomeAssistantError()
|
||||
|
||||
@process_lock
|
||||
async def restart(self) -> None:
|
||||
@ -433,7 +433,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
try:
|
||||
await self.instance.restart()
|
||||
except DockerAPIError:
|
||||
raise HomeAssistantError() from None
|
||||
raise HomeAssistantError()
|
||||
|
||||
await self._block_till_run(self.version)
|
||||
|
||||
@ -459,7 +459,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
try:
|
||||
return await self.instance.stats()
|
||||
except DockerAPIError:
|
||||
raise HomeAssistantError() from None
|
||||
raise HomeAssistantError()
|
||||
|
||||
def is_running(self) -> Awaitable[bool]:
|
||||
"""Return True if Docker container is running.
|
||||
|
@ -76,7 +76,7 @@ class AppArmorControl(CoreSysAttributes):
|
||||
shutil.copyfile(profile_file, dest_profile)
|
||||
except OSError as err:
|
||||
_LOGGER.error("Can't copy %s: %s", profile_file, err)
|
||||
raise HostAppArmorError() from None
|
||||
raise HostAppArmorError()
|
||||
|
||||
# Load profiles
|
||||
_LOGGER.info("Add or Update AppArmor profile: %s", profile_name)
|
||||
|
@ -96,4 +96,4 @@ class InfoCenter(CoreSysAttributes):
|
||||
_LOGGER.warning("Can't update host system information!")
|
||||
except DBusNotConnectedError:
|
||||
_LOGGER.error("No hostname D-Bus connection available")
|
||||
raise HostNotSupportedError() from None
|
||||
raise HostNotSupportedError()
|
||||
|
@ -36,4 +36,4 @@ class NetworkManager(CoreSysAttributes):
|
||||
_LOGGER.warning("Can't update host DNS system information!")
|
||||
except DBusNotConnectedError:
|
||||
_LOGGER.error("No hostname D-Bus connection available")
|
||||
raise HostNotSupportedError() from None
|
||||
raise HostNotSupportedError()
|
||||
|
@ -117,10 +117,10 @@ class SoundControl(CoreSysAttributes):
|
||||
|
||||
except PulseIndexError:
|
||||
_LOGGER.error("Can't find %s stream %s", source, name)
|
||||
raise PulseAudioError() from None
|
||||
raise PulseAudioError()
|
||||
except PulseError as err:
|
||||
_LOGGER.error("Can't set %s as stream: %s", name, err)
|
||||
raise PulseAudioError() from None
|
||||
raise PulseAudioError()
|
||||
|
||||
# Run and Reload data
|
||||
await self.sys_run_in_executor(_set_default)
|
||||
@ -151,10 +151,10 @@ class SoundControl(CoreSysAttributes):
|
||||
_LOGGER.error(
|
||||
"Can't find %s stream %d (App: %s)", stream_type, index, application
|
||||
)
|
||||
raise PulseAudioError() from None
|
||||
raise PulseAudioError()
|
||||
except PulseError as err:
|
||||
_LOGGER.error("Can't set %d volume: %s", index, err)
|
||||
raise PulseAudioError() from None
|
||||
raise PulseAudioError()
|
||||
|
||||
# Run and Reload data
|
||||
await self.sys_run_in_executor(_set_volume)
|
||||
@ -185,10 +185,10 @@ class SoundControl(CoreSysAttributes):
|
||||
_LOGGER.error(
|
||||
"Can't find %s stream %d (App: %s)", stream_type, index, application
|
||||
)
|
||||
raise PulseAudioError() from None
|
||||
raise PulseAudioError()
|
||||
except PulseError as err:
|
||||
_LOGGER.error("Can't set %d volume: %s", index, err)
|
||||
raise PulseAudioError() from None
|
||||
raise PulseAudioError()
|
||||
|
||||
# Run and Reload data
|
||||
await self.sys_run_in_executor(_set_mute)
|
||||
@ -205,12 +205,12 @@ class SoundControl(CoreSysAttributes):
|
||||
|
||||
except PulseIndexError:
|
||||
_LOGGER.error("Can't find %s profile %s", card_name, profile_name)
|
||||
raise PulseAudioError() from None
|
||||
raise PulseAudioError()
|
||||
except PulseError as err:
|
||||
_LOGGER.error(
|
||||
"Can't activate %s profile %s: %s", card_name, profile_name, err
|
||||
)
|
||||
raise PulseAudioError() from None
|
||||
raise PulseAudioError()
|
||||
|
||||
# Run and Reload data
|
||||
await self.sys_run_in_executor(_activate_profile)
|
||||
@ -331,7 +331,7 @@ class SoundControl(CoreSysAttributes):
|
||||
|
||||
except PulseOperationFailed as err:
|
||||
_LOGGER.error("Error while processing pulse update: %s", err)
|
||||
raise PulseAudioError() from None
|
||||
raise PulseAudioError()
|
||||
except PulseError as err:
|
||||
_LOGGER.debug("Can't update PulseAudio data: %s", err)
|
||||
|
||||
|
@ -77,7 +77,7 @@ class Ingress(JsonConfig, CoreSysAttributes):
|
||||
try:
|
||||
valid_dt = utc_from_timestamp(valid)
|
||||
except OverflowError:
|
||||
_LOGGER.warning("Session timestamp %f is invalid!", valid_dt)
|
||||
_LOGGER.warning("Session timestamp %f is invalid!", valid)
|
||||
continue
|
||||
|
||||
if valid_dt < now:
|
||||
@ -115,7 +115,7 @@ class Ingress(JsonConfig, CoreSysAttributes):
|
||||
try:
|
||||
valid_until = utc_from_timestamp(self.sessions[session])
|
||||
except OverflowError:
|
||||
_LOGGER.warning("Session timestamp %f is invalid!", valid_until)
|
||||
_LOGGER.warning("Session timestamp %f is invalid!", self.sessions[session])
|
||||
return False
|
||||
|
||||
# Is still valid?
|
||||
|
@ -31,7 +31,7 @@ def filter_data(coresys: CoreSys, event: dict, hint: dict) -> dict:
|
||||
return None
|
||||
|
||||
# Ignore issue if system is not supported or diagnostics is disabled
|
||||
if not coresys.config.diagnostics or not coresys.supported or dev_env:
|
||||
if not coresys.config.diagnostics or not coresys.core.supported or dev_env:
|
||||
return None
|
||||
|
||||
# Not full startup - missing information
|
||||
|
@ -156,7 +156,7 @@ class Audio(JsonConfig, CoreSysAttributes):
|
||||
await self.instance.update(version, image=self.sys_updater.image_audio)
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Audio update fails")
|
||||
raise AudioUpdateError() from None
|
||||
raise AudioUpdateError()
|
||||
else:
|
||||
self.version = version
|
||||
self.image = self.sys_updater.image_audio
|
||||
@ -176,7 +176,7 @@ class Audio(JsonConfig, CoreSysAttributes):
|
||||
await self.instance.restart()
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Can't start Audio plugin")
|
||||
raise AudioError() from None
|
||||
raise AudioError()
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Run CoreDNS."""
|
||||
@ -185,7 +185,7 @@ class Audio(JsonConfig, CoreSysAttributes):
|
||||
await self.instance.run()
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Can't start Audio plugin")
|
||||
raise AudioError() from None
|
||||
raise AudioError()
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""Stop CoreDNS."""
|
||||
@ -194,7 +194,7 @@ class Audio(JsonConfig, CoreSysAttributes):
|
||||
await self.instance.stop()
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Can't stop Audio plugin")
|
||||
raise AudioError() from None
|
||||
raise AudioError()
|
||||
|
||||
def logs(self) -> Awaitable[bytes]:
|
||||
"""Get CoreDNS docker logs.
|
||||
@ -208,7 +208,7 @@ class Audio(JsonConfig, CoreSysAttributes):
|
||||
try:
|
||||
return await self.instance.stats()
|
||||
except DockerAPIError:
|
||||
raise AudioError() from None
|
||||
raise AudioError()
|
||||
|
||||
def is_running(self) -> Awaitable[bool]:
|
||||
"""Return True if Docker container is running.
|
||||
|
@ -134,7 +134,7 @@ class HaCli(CoreSysAttributes, JsonConfig):
|
||||
)
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("HA cli update fails")
|
||||
raise CliUpdateError() from None
|
||||
raise CliUpdateError()
|
||||
else:
|
||||
self.version = version
|
||||
self.image = self.sys_updater.image_cli
|
||||
@ -159,7 +159,7 @@ class HaCli(CoreSysAttributes, JsonConfig):
|
||||
await self.instance.run()
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Can't start cli plugin")
|
||||
raise CliError() from None
|
||||
raise CliError()
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""Stop cli."""
|
||||
@ -168,14 +168,14 @@ class HaCli(CoreSysAttributes, JsonConfig):
|
||||
await self.instance.stop()
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Can't stop cli plugin")
|
||||
raise CliError() from None
|
||||
raise CliError()
|
||||
|
||||
async def stats(self) -> DockerStats:
|
||||
"""Return stats of cli."""
|
||||
try:
|
||||
return await self.instance.stats()
|
||||
except DockerAPIError:
|
||||
raise CliError() from None
|
||||
raise CliError()
|
||||
|
||||
def is_running(self) -> Awaitable[bool]:
|
||||
"""Return True if Docker container is running.
|
||||
|
@ -207,7 +207,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
||||
await self.instance.update(version, image=self.sys_updater.image_dns)
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("CoreDNS update fails")
|
||||
raise CoreDNSUpdateError() from None
|
||||
raise CoreDNSUpdateError()
|
||||
else:
|
||||
self.version = version
|
||||
self.image = self.sys_updater.image_dns
|
||||
@ -240,7 +240,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
||||
await self.instance.run()
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Can't start CoreDNS plugin")
|
||||
raise CoreDNSError() from None
|
||||
raise CoreDNSError()
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""Stop CoreDNS."""
|
||||
@ -249,7 +249,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
||||
await self.instance.stop()
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Can't stop CoreDNS plugin")
|
||||
raise CoreDNSError() from None
|
||||
raise CoreDNSError()
|
||||
|
||||
async def reset(self) -> None:
|
||||
"""Reset DNS and hosts."""
|
||||
@ -316,7 +316,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
||||
self.corefile.write_text(data)
|
||||
except OSError as err:
|
||||
_LOGGER.error("Can't update corefile: %s", err)
|
||||
raise CoreDNSError() from None
|
||||
raise CoreDNSError()
|
||||
|
||||
def _init_hosts(self) -> None:
|
||||
"""Import hosts entry."""
|
||||
@ -340,7 +340,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
||||
hosts.write(f"{entry.ip_address!s} {' '.join(entry.names)}\n")
|
||||
except OSError as err:
|
||||
_LOGGER.error("Can't write hosts file: %s", err)
|
||||
raise CoreDNSError() from None
|
||||
raise CoreDNSError()
|
||||
|
||||
def add_host(self, ipv4: IPv4Address, names: List[str], write: bool = True) -> None:
|
||||
"""Add a new host entry."""
|
||||
@ -404,7 +404,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
||||
try:
|
||||
return await self.instance.stats()
|
||||
except DockerAPIError:
|
||||
raise CoreDNSError() from None
|
||||
raise CoreDNSError()
|
||||
|
||||
def is_running(self) -> Awaitable[bool]:
|
||||
"""Return True if Docker container is running.
|
||||
|
@ -129,7 +129,7 @@ class Multicast(JsonConfig, CoreSysAttributes):
|
||||
await self.instance.update(version, image=self.sys_updater.image_multicast)
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Multicast update fails")
|
||||
raise MulticastUpdateError() from None
|
||||
raise MulticastUpdateError()
|
||||
else:
|
||||
self.version = version
|
||||
self.image = self.sys_updater.image_multicast
|
||||
@ -181,7 +181,7 @@ class Multicast(JsonConfig, CoreSysAttributes):
|
||||
try:
|
||||
return await self.instance.stats()
|
||||
except DockerAPIError:
|
||||
raise MulticastError() from None
|
||||
raise MulticastError()
|
||||
|
||||
def is_running(self) -> Awaitable[bool]:
|
||||
"""Return True if Docker container is running.
|
||||
|
@ -277,7 +277,7 @@ class Snapshot(CoreSysAttributes):
|
||||
_LOGGER.error(
|
||||
"Invalid data for %s: %s", self.tarfile, humanize_error(self._data, err)
|
||||
)
|
||||
raise ValueError("Invalid config") from None
|
||||
raise ValueError("Invalid config")
|
||||
|
||||
# new snapshot, build it
|
||||
def _create_snapshot():
|
||||
|
@ -87,7 +87,7 @@ class Supervisor(CoreSysAttributes):
|
||||
|
||||
except (aiohttp.ClientError, asyncio.TimeoutError) as err:
|
||||
_LOGGER.warning("Can't fetch AppArmor profile: %s", err)
|
||||
raise SupervisorError() from None
|
||||
raise SupervisorError()
|
||||
|
||||
with TemporaryDirectory(dir=self.sys_config.path_tmp) as tmp_dir:
|
||||
profile_file = Path(tmp_dir, "apparmor.txt")
|
||||
@ -95,7 +95,7 @@ class Supervisor(CoreSysAttributes):
|
||||
profile_file.write_text(data)
|
||||
except OSError as err:
|
||||
_LOGGER.error("Can't write temporary profile: %s", err)
|
||||
raise SupervisorError() from None
|
||||
raise SupervisorError()
|
||||
|
||||
try:
|
||||
await self.sys_host.apparmor.load_profile(
|
||||
@ -103,7 +103,7 @@ class Supervisor(CoreSysAttributes):
|
||||
)
|
||||
except HostAppArmorError:
|
||||
_LOGGER.error("Can't update AppArmor profile!")
|
||||
raise SupervisorError() from None
|
||||
raise SupervisorError()
|
||||
|
||||
async def update(self, version: Optional[str] = None) -> None:
|
||||
"""Update Home Assistant version."""
|
||||
@ -123,7 +123,7 @@ class Supervisor(CoreSysAttributes):
|
||||
)
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Update of Supervisor fails!")
|
||||
raise SupervisorUpdateError() from None
|
||||
raise SupervisorUpdateError()
|
||||
else:
|
||||
self.sys_config.version = version
|
||||
self.sys_config.save_data()
|
||||
@ -149,7 +149,7 @@ class Supervisor(CoreSysAttributes):
|
||||
try:
|
||||
return await self.instance.stats()
|
||||
except DockerAPIError:
|
||||
raise SupervisorError() from None
|
||||
raise SupervisorError()
|
||||
|
||||
async def repair(self):
|
||||
"""Repair local Supervisor data."""
|
||||
|
@ -158,16 +158,16 @@ class Updater(JsonConfig, CoreSysAttributes):
|
||||
|
||||
except (aiohttp.ClientError, asyncio.TimeoutError) as err:
|
||||
_LOGGER.warning("Can't fetch versions from %s: %s", url, err)
|
||||
raise HassioUpdaterError() from None
|
||||
raise HassioUpdaterError()
|
||||
|
||||
except json.JSONDecodeError as err:
|
||||
_LOGGER.warning("Can't parse versions from %s: %s", url, err)
|
||||
raise HassioUpdaterError() from None
|
||||
raise HassioUpdaterError()
|
||||
|
||||
# data valid?
|
||||
if not data or data.get(ATTR_CHANNEL) != self.channel:
|
||||
_LOGGER.warning("Invalid data from %s", url)
|
||||
raise HassioUpdaterError() from None
|
||||
raise HassioUpdaterError()
|
||||
|
||||
try:
|
||||
# Update supervisor version
|
||||
@ -196,7 +196,7 @@ class Updater(JsonConfig, CoreSysAttributes):
|
||||
|
||||
except KeyError as err:
|
||||
_LOGGER.warning("Can't process version data: %s", err)
|
||||
raise HassioUpdaterError() from None
|
||||
raise HassioUpdaterError()
|
||||
|
||||
else:
|
||||
self.save_data()
|
||||
|
@ -89,7 +89,7 @@ class DBus:
|
||||
except ET.ParseError as err:
|
||||
_LOGGER.error("Can't parse introspect data: %s", err)
|
||||
_LOGGER.debug("Introspect %s on %s", self.bus_name, self.object_path)
|
||||
raise DBusParseError() from None
|
||||
raise DBusParseError()
|
||||
|
||||
# Read available methods
|
||||
for interface in xml.findall("./interface"):
|
||||
@ -137,7 +137,7 @@ class DBus:
|
||||
except json.JSONDecodeError as err:
|
||||
_LOGGER.error("Can't parse '%s': %s", json_raw, err)
|
||||
_LOGGER.debug("GVariant data: '%s'", raw)
|
||||
raise DBusParseError() from None
|
||||
raise DBusParseError()
|
||||
|
||||
@staticmethod
|
||||
def gvariant_args(args: List[Any]) -> str:
|
||||
@ -179,7 +179,7 @@ class DBus:
|
||||
return (await self.call_dbus(DBUS_METHOD_GETALL, interface))[0]
|
||||
except IndexError:
|
||||
_LOGGER.error("No attributes returned for %s", interface)
|
||||
raise DBusFatalError from None
|
||||
raise DBusFatalError
|
||||
|
||||
async def _send(self, command: List[str]) -> str:
|
||||
"""Send command over dbus."""
|
||||
@ -196,7 +196,7 @@ class DBus:
|
||||
data, error = await proc.communicate()
|
||||
except OSError as err:
|
||||
_LOGGER.error("DBus fatal error: %s", err)
|
||||
raise DBusFatalError() from None
|
||||
raise DBusFatalError()
|
||||
|
||||
# Success?
|
||||
if proc.returncode == 0:
|
||||
@ -301,7 +301,7 @@ class DBusSignalWrapper:
|
||||
try:
|
||||
data = await self._proc.stdout.readline()
|
||||
except asyncio.TimeoutError:
|
||||
raise StopAsyncIteration() from None
|
||||
raise StopAsyncIteration()
|
||||
|
||||
# Program close
|
||||
if not data:
|
||||
@ -322,4 +322,4 @@ class DBusSignalWrapper:
|
||||
try:
|
||||
return self.dbus.parse_gvariant(data)
|
||||
except DBusParseError:
|
||||
raise StopAsyncIteration() from None
|
||||
raise StopAsyncIteration()
|
||||
|
@ -20,7 +20,7 @@ def write_json_file(jsonfile: Path, data: Any) -> None:
|
||||
jsonfile.write_text(json.dumps(data, indent=2))
|
||||
except (OSError, ValueError, TypeError) as err:
|
||||
_LOGGER.error("Can't write %s: %s", jsonfile, err)
|
||||
raise JsonFileError() from None
|
||||
raise JsonFileError()
|
||||
|
||||
|
||||
def read_json_file(jsonfile: Path) -> Any:
|
||||
@ -29,7 +29,7 @@ def read_json_file(jsonfile: Path) -> Any:
|
||||
return json.loads(jsonfile.read_text())
|
||||
except (OSError, ValueError, TypeError, UnicodeDecodeError) as err:
|
||||
_LOGGER.error("Can't read json from %s: %s", jsonfile, err)
|
||||
raise JsonFileError() from None
|
||||
raise JsonFileError()
|
||||
|
||||
|
||||
class JsonConfig:
|
||||
|
@ -24,6 +24,6 @@ def validate_timezone(timezone):
|
||||
raise vol.Invalid(
|
||||
"Invalid time zone passed in. Valid options can be found here: "
|
||||
"http://en.wikipedia.org/wiki/List_of_tz_database_time_zones"
|
||||
) from None
|
||||
)
|
||||
|
||||
return timezone
|
||||
|
@ -4,15 +4,26 @@ from unittest.mock import MagicMock, PropertyMock, patch
|
||||
import pytest
|
||||
|
||||
from supervisor.bootstrap import initialize_coresys
|
||||
from supervisor.docker import DockerAPI
|
||||
|
||||
# pylint: disable=redefined-outer-name, protected-access
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def docker():
|
||||
"""Mock Docker API."""
|
||||
with patch("supervisor.coresys.DockerAPI") as mock:
|
||||
yield mock
|
||||
"""Mock DockerAPI."""
|
||||
images = [MagicMock(tags=["homeassistant/amd64-hassio-supervisor:latest"])]
|
||||
|
||||
with patch("docker.DockerClient", return_value=MagicMock()), patch(
|
||||
"supervisor.docker.DockerAPI.images", return_value=MagicMock()
|
||||
), patch("supervisor.docker.DockerAPI.containers", return_value=MagicMock()), patch(
|
||||
"supervisor.docker.DockerAPI.api", return_value=MagicMock()
|
||||
), patch(
|
||||
"supervisor.docker.DockerAPI.images.list", return_value=images
|
||||
):
|
||||
docker_obj = DockerAPI()
|
||||
|
||||
yield docker_obj
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
15
tests/docker/test_denylist.py
Normal file
15
tests/docker/test_denylist.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""Test tags in denylist."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
def test_has_images_in_denylist(docker):
|
||||
"""Test tags in denylist exsist."""
|
||||
images = [MagicMock(tags=["containrrr/watchtower:latest"])]
|
||||
with patch("supervisor.docker.DockerAPI.images.list", return_value=images):
|
||||
assert docker.check_denylist_images()
|
||||
|
||||
|
||||
def test_no_images_in_denylist(docker):
|
||||
"""Test tags in denylist does not exsist."""
|
||||
assert not docker.check_denylist_images()
|
@ -17,21 +17,21 @@ def test_ignored_exception(coresys):
|
||||
def test_diagnostics_disabled(coresys):
|
||||
"""Test if diagnostics is disabled."""
|
||||
coresys.config.diagnostics = False
|
||||
coresys.supported = True
|
||||
coresys.core.supported = True
|
||||
assert filter_data(coresys, SAMPLE_EVENT, {}) is None
|
||||
|
||||
|
||||
def test_not_supported(coresys):
|
||||
"""Test if not supported."""
|
||||
coresys.config.diagnostics = True
|
||||
coresys.supported = False
|
||||
coresys.core.supported = False
|
||||
assert filter_data(coresys, SAMPLE_EVENT, {}) is None
|
||||
|
||||
|
||||
def test_is_dev(coresys):
|
||||
"""Test if dev."""
|
||||
coresys.config.diagnostics = True
|
||||
coresys.supported = True
|
||||
coresys.core.supported = True
|
||||
with patch("os.environ", return_value=[("ENV_SUPERVISOR_DEV", "1")]):
|
||||
assert filter_data(coresys, SAMPLE_EVENT, {}) is None
|
||||
|
||||
@ -39,7 +39,7 @@ def test_is_dev(coresys):
|
||||
def test_not_started(coresys):
|
||||
"""Test if supervisor not fully started."""
|
||||
coresys.config.diagnostics = True
|
||||
coresys.supported = True
|
||||
coresys.core.supported = True
|
||||
|
||||
coresys.core.state = CoreStates.INITIALIZE
|
||||
assert filter_data(coresys, SAMPLE_EVENT, {}) == SAMPLE_EVENT
|
||||
|
Loading…
x
Reference in New Issue
Block a user