mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-24 09:36:31 +00:00
commit
0c57df0c8e
5
API.md
5
API.md
@ -659,7 +659,10 @@ authentication system. Needs an ingress session as cookie.
|
||||
"uuid": "uuid",
|
||||
"config": {}
|
||||
}
|
||||
]
|
||||
],
|
||||
"services": {
|
||||
"ozw": ["core_zwave"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -6,7 +6,7 @@ colorlog==4.1.0
|
||||
cpe==1.2.1
|
||||
cryptography==2.9.2
|
||||
docker==4.2.0
|
||||
gitpython==3.1.2
|
||||
gitpython==3.1.3
|
||||
jinja2==2.11.2
|
||||
packaging==20.4
|
||||
ptvsd==4.3.2
|
||||
|
@ -5,7 +5,7 @@ import logging
|
||||
import tarfile
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from ..const import BOOT_AUTO, STATE_STARTED
|
||||
from ..const import BOOT_AUTO, STATE_STARTED, AddonStartup
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import (
|
||||
AddonsError,
|
||||
@ -78,30 +78,49 @@ class AddonManager(CoreSysAttributes):
|
||||
# Sync DNS
|
||||
await self.sync_dns()
|
||||
|
||||
async def boot(self, stage: str) -> None:
|
||||
async def boot(self, stage: AddonStartup) -> None:
|
||||
"""Boot add-ons with mode auto."""
|
||||
tasks = []
|
||||
tasks: List[Addon] = []
|
||||
for addon in self.installed:
|
||||
if addon.boot != BOOT_AUTO or addon.startup != stage:
|
||||
continue
|
||||
tasks.append(addon.start())
|
||||
tasks.append(addon)
|
||||
|
||||
# Evaluate add-ons which need to be started
|
||||
_LOGGER.info("Phase '%s' start %d add-ons", stage, len(tasks))
|
||||
if tasks:
|
||||
await asyncio.wait(tasks)
|
||||
await asyncio.sleep(self.sys_config.wait_boot)
|
||||
if not tasks:
|
||||
return
|
||||
|
||||
async def shutdown(self, stage: str) -> None:
|
||||
# Start Add-ons sequential
|
||||
# avoid issue on slow IO
|
||||
for addon in tasks:
|
||||
try:
|
||||
await addon.start()
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
_LOGGER.warning("Can't start Add-on %s: %s", addon.slug, err)
|
||||
|
||||
await asyncio.sleep(self.sys_config.wait_boot)
|
||||
|
||||
async def shutdown(self, stage: AddonStartup) -> None:
|
||||
"""Shutdown addons."""
|
||||
tasks = []
|
||||
tasks: List[Addon] = []
|
||||
for addon in self.installed:
|
||||
if await addon.state() != STATE_STARTED or addon.startup != stage:
|
||||
continue
|
||||
tasks.append(addon.stop())
|
||||
tasks.append(addon)
|
||||
|
||||
# Evaluate add-ons which need to be stopped
|
||||
_LOGGER.info("Phase '%s' stop %d add-ons", stage, len(tasks))
|
||||
if tasks:
|
||||
await asyncio.wait(tasks)
|
||||
if not tasks:
|
||||
return
|
||||
|
||||
# Stop Add-ons sequential
|
||||
# avoid issue on slow IO
|
||||
for addon in tasks:
|
||||
try:
|
||||
await addon.stop()
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
_LOGGER.warning("Can't stop Add-on %s: %s", addon.slug, err)
|
||||
|
||||
async def install(self, slug: str) -> None:
|
||||
"""Install an add-on."""
|
||||
|
@ -81,7 +81,7 @@ class Addon(AddonModel):
|
||||
|
||||
@property
|
||||
def ip_address(self) -> IPv4Address:
|
||||
"""Return IP of Add-on instance."""
|
||||
"""Return IP of add-on instance."""
|
||||
return self.instance.ip_address
|
||||
|
||||
@property
|
||||
@ -366,7 +366,7 @@ class Addon(AddonModel):
|
||||
write_json_file(self.path_options, options)
|
||||
except vol.Invalid as ex:
|
||||
_LOGGER.error(
|
||||
"Add-on %s have wrong options: %s",
|
||||
"Add-on %s has invalid options: %s",
|
||||
self.slug,
|
||||
humanize_error(options, ex),
|
||||
)
|
||||
@ -524,7 +524,7 @@ class Addon(AddonModel):
|
||||
Return a coroutine.
|
||||
"""
|
||||
if not self.with_stdin:
|
||||
_LOGGER.error("Add-on don't support write to stdin!")
|
||||
_LOGGER.error("Add-on %s does not support writing to stdin!", self.slug)
|
||||
raise AddonsNotSupportedError()
|
||||
|
||||
try:
|
||||
@ -622,7 +622,7 @@ class Addon(AddonModel):
|
||||
|
||||
# If available
|
||||
if not self._available(data[ATTR_SYSTEM]):
|
||||
_LOGGER.error("Add-on %s is not available for this Platform", self.slug)
|
||||
_LOGGER.error("Add-on %s is not available for this platform", self.slug)
|
||||
raise AddonsNotSupportedError()
|
||||
|
||||
# Restore local add-on information
|
||||
@ -673,7 +673,9 @@ class Addon(AddonModel):
|
||||
try:
|
||||
await self.sys_host.apparmor.load_profile(self.slug, profile_file)
|
||||
except HostAppArmorError:
|
||||
_LOGGER.error("Can't restore AppArmor profile")
|
||||
_LOGGER.error(
|
||||
"Can't restore AppArmor profile for add-on %s", self.slug
|
||||
)
|
||||
raise AddonsError() from None
|
||||
|
||||
# Run add-on
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""Init file for Supervisor add-ons."""
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import Any, Awaitable, Dict, List, Optional
|
||||
|
||||
@ -64,6 +65,7 @@ from ..const import (
|
||||
SECURITY_DISABLE,
|
||||
SECURITY_PROFILE,
|
||||
AddonStages,
|
||||
AddonStartup,
|
||||
)
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from .validate import RE_SERVICE, RE_VOLUME, schema_ui_options, validate_options
|
||||
@ -71,7 +73,7 @@ from .validate import RE_SERVICE, RE_VOLUME, schema_ui_options, validate_options
|
||||
Data = Dict[str, Any]
|
||||
|
||||
|
||||
class AddonModel(CoreSysAttributes):
|
||||
class AddonModel(CoreSysAttributes, ABC):
|
||||
"""Add-on Data layout."""
|
||||
|
||||
def __init__(self, coresys: CoreSys, slug: str):
|
||||
@ -80,19 +82,19 @@ class AddonModel(CoreSysAttributes):
|
||||
self.slug: str = slug
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def data(self) -> Data:
|
||||
"""Return Add-on config/data."""
|
||||
raise NotImplementedError()
|
||||
"""Return add-on config/data."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def is_installed(self) -> bool:
|
||||
"""Return True if an add-on is installed."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def is_detached(self) -> bool:
|
||||
"""Return True if add-on is detached."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
@ -193,9 +195,9 @@ class AddonModel(CoreSysAttributes):
|
||||
return True
|
||||
|
||||
@property
|
||||
def startup(self) -> Optional[str]:
|
||||
def startup(self) -> AddonStartup:
|
||||
"""Return startup type of add-on."""
|
||||
return self.data.get(ATTR_STARTUP)
|
||||
return self.data[ATTR_STARTUP]
|
||||
|
||||
@property
|
||||
def advanced(self) -> bool:
|
||||
|
@ -85,12 +85,10 @@ from ..const import (
|
||||
PRIVILEGED_ALL,
|
||||
ROLE_ALL,
|
||||
ROLE_DEFAULT,
|
||||
STARTUP_ALL,
|
||||
STARTUP_APPLICATION,
|
||||
STARTUP_SERVICES,
|
||||
STATE_STARTED,
|
||||
STATE_STOPPED,
|
||||
AddonStages,
|
||||
AddonStartup,
|
||||
)
|
||||
from ..coresys import CoreSys
|
||||
from ..discovery.validate import valid_discovery_service
|
||||
@ -169,12 +167,12 @@ MACHINE_ALL = [
|
||||
]
|
||||
|
||||
|
||||
def _simple_startup(value):
|
||||
def _simple_startup(value) -> str:
|
||||
"""Define startup schema."""
|
||||
if value == "before":
|
||||
return STARTUP_SERVICES
|
||||
return AddonStartup.SERVICES.value
|
||||
if value == "after":
|
||||
return STARTUP_APPLICATION
|
||||
return AddonStartup.APPLICATION.value
|
||||
return value
|
||||
|
||||
|
||||
@ -188,7 +186,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema(
|
||||
vol.Required(ATTR_ARCH): [vol.In(ARCH_ALL)],
|
||||
vol.Optional(ATTR_MACHINE): [vol.In(MACHINE_ALL)],
|
||||
vol.Optional(ATTR_URL): vol.Url(),
|
||||
vol.Required(ATTR_STARTUP): vol.All(_simple_startup, vol.In(STARTUP_ALL)),
|
||||
vol.Required(ATTR_STARTUP): vol.All(_simple_startup, vol.Coerce(AddonStartup)),
|
||||
vol.Required(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
||||
vol.Optional(ATTR_INIT, default=True): vol.Boolean(),
|
||||
vol.Optional(ATTR_ADVANCED, default=False): vol.Boolean(),
|
||||
|
@ -137,10 +137,10 @@ class APIAddons(CoreSysAttributes):
|
||||
|
||||
addon = self.sys_addons.get(addon_slug)
|
||||
if not addon:
|
||||
raise APIError("Addon does not exist")
|
||||
raise APIError(f"Addon {addon_slug} does not exist", addon_slug)
|
||||
|
||||
if check_installed and not addon.is_installed:
|
||||
raise APIError("Addon is not installed")
|
||||
raise APIError(f"Addon {addon.slug} is not installed")
|
||||
|
||||
return addon
|
||||
|
||||
@ -398,7 +398,7 @@ class APIAddons(CoreSysAttributes):
|
||||
"""Return icon from add-on."""
|
||||
addon: AnyAddon = self._extract_addon(request, check_installed=False)
|
||||
if not addon.with_icon:
|
||||
raise APIError("No icon found!")
|
||||
raise APIError(f"No icon found for add-on {addon.slug}!")
|
||||
|
||||
with addon.path_icon.open("rb") as png:
|
||||
return png.read()
|
||||
@ -408,7 +408,7 @@ class APIAddons(CoreSysAttributes):
|
||||
"""Return logo from add-on."""
|
||||
addon: AnyAddon = self._extract_addon(request, check_installed=False)
|
||||
if not addon.with_logo:
|
||||
raise APIError("No logo found!")
|
||||
raise APIError(f"No logo found for add-on {addon.slug}!")
|
||||
|
||||
with addon.path_logo.open("rb") as png:
|
||||
return png.read()
|
||||
@ -418,7 +418,7 @@ class APIAddons(CoreSysAttributes):
|
||||
"""Return changelog from add-on."""
|
||||
addon: AnyAddon = self._extract_addon(request, check_installed=False)
|
||||
if not addon.with_changelog:
|
||||
raise APIError("No changelog found!")
|
||||
raise APIError(f"No changelog found for add-on {addon.slug}!")
|
||||
|
||||
with addon.path_changelog.open("r") as changelog:
|
||||
return changelog.read()
|
||||
@ -428,7 +428,7 @@ class APIAddons(CoreSysAttributes):
|
||||
"""Return documentation from add-on."""
|
||||
addon: AnyAddon = self._extract_addon(request, check_installed=False)
|
||||
if not addon.with_documentation:
|
||||
raise APIError("No documentation found!")
|
||||
raise APIError(f"No documentation found for add-on {addon.slug}!")
|
||||
|
||||
with addon.path_documentation.open("r") as documentation:
|
||||
return documentation.read()
|
||||
@ -438,7 +438,7 @@ class APIAddons(CoreSysAttributes):
|
||||
"""Write to stdin of add-on."""
|
||||
addon: AnyAddon = self._extract_addon(request)
|
||||
if not addon.with_stdin:
|
||||
raise APIError("STDIN not supported by add-on")
|
||||
raise APIError(f"STDIN not supported the {addon.slug} add-on")
|
||||
|
||||
data = await request.read()
|
||||
await asyncio.shield(addon.write_stdin(data))
|
||||
|
@ -5,6 +5,7 @@ from ..const import (
|
||||
ATTR_ADDON,
|
||||
ATTR_CONFIG,
|
||||
ATTR_DISCOVERY,
|
||||
ATTR_SERVICES,
|
||||
ATTR_SERVICE,
|
||||
ATTR_UUID,
|
||||
REQUEST_FROM,
|
||||
@ -42,6 +43,7 @@ class APIDiscovery(CoreSysAttributes):
|
||||
"""Show register services."""
|
||||
self._check_permission_ha(request)
|
||||
|
||||
# Get available discovery
|
||||
discovery = []
|
||||
for message in self.sys_discovery.list_messages:
|
||||
discovery.append(
|
||||
@ -53,7 +55,13 @@ class APIDiscovery(CoreSysAttributes):
|
||||
}
|
||||
)
|
||||
|
||||
return {ATTR_DISCOVERY: discovery}
|
||||
# Get available services/add-ons
|
||||
services = {}
|
||||
for addon in self.sys_addons.all:
|
||||
for name in addon.discovery:
|
||||
services.setdefault(name, []).append(addon.slug)
|
||||
|
||||
return {ATTR_DISCOVERY: discovery, ATTR_SERVICES: services}
|
||||
|
||||
@api_process
|
||||
async def set_discovery(self, request):
|
||||
|
@ -75,6 +75,8 @@ class APIProxy(CoreSysAttributes):
|
||||
async def stream(self, request: web.Request):
|
||||
"""Proxy HomeAssistant EventStream Requests."""
|
||||
self._check_access(request)
|
||||
if not await self.sys_homeassistant.check_api_state():
|
||||
raise HTTPBadGateway()
|
||||
|
||||
_LOGGER.info("Home Assistant EventStream start")
|
||||
async with self._api_client(request, "stream", timeout=None) as client:
|
||||
@ -94,6 +96,8 @@ class APIProxy(CoreSysAttributes):
|
||||
async def api(self, request: web.Request):
|
||||
"""Proxy Home Assistant API Requests."""
|
||||
self._check_access(request)
|
||||
if not await self.sys_homeassistant.check_api_state():
|
||||
raise HTTPBadGateway()
|
||||
|
||||
# Normal request
|
||||
path = request.match_info.get("path", "")
|
||||
@ -153,6 +157,8 @@ class APIProxy(CoreSysAttributes):
|
||||
|
||||
async def websocket(self, request: web.Request):
|
||||
"""Initialize a WebSocket API connection."""
|
||||
if not await self.sys_homeassistant.check_api_state():
|
||||
raise HTTPBadGateway()
|
||||
_LOGGER.info("Home Assistant WebSocket API request initialize")
|
||||
|
||||
# init server
|
||||
|
@ -3,7 +3,7 @@ from enum import Enum
|
||||
from ipaddress import ip_network
|
||||
from pathlib import Path
|
||||
|
||||
SUPERVISOR_VERSION = "225"
|
||||
SUPERVISOR_VERSION = "226"
|
||||
|
||||
|
||||
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
|
||||
@ -245,20 +245,6 @@ PROVIDE_SERVICE = "provide"
|
||||
NEED_SERVICE = "need"
|
||||
WANT_SERVICE = "want"
|
||||
|
||||
STARTUP_INITIALIZE = "initialize"
|
||||
STARTUP_SYSTEM = "system"
|
||||
STARTUP_SERVICES = "services"
|
||||
STARTUP_APPLICATION = "application"
|
||||
STARTUP_ONCE = "once"
|
||||
|
||||
STARTUP_ALL = [
|
||||
STARTUP_ONCE,
|
||||
STARTUP_INITIALIZE,
|
||||
STARTUP_SYSTEM,
|
||||
STARTUP_SERVICES,
|
||||
STARTUP_APPLICATION,
|
||||
]
|
||||
|
||||
BOOT_AUTO = "auto"
|
||||
BOOT_MANUAL = "manual"
|
||||
|
||||
@ -339,6 +325,16 @@ CHAN_ID = "chan_id"
|
||||
CHAN_TYPE = "chan_type"
|
||||
|
||||
|
||||
class AddonStartup(str, Enum):
|
||||
"""Startup types of Add-on."""
|
||||
|
||||
INITIALIZE = "initialize"
|
||||
SYSTEM = "system"
|
||||
SERVICES = "services"
|
||||
APPLICATION = "application"
|
||||
ONCE = "once"
|
||||
|
||||
|
||||
class AddonStages(str, Enum):
|
||||
"""Stage types of add-on."""
|
||||
|
||||
|
@ -5,13 +5,7 @@ import logging
|
||||
|
||||
import async_timeout
|
||||
|
||||
from .const import (
|
||||
STARTUP_APPLICATION,
|
||||
STARTUP_INITIALIZE,
|
||||
STARTUP_SERVICES,
|
||||
STARTUP_SYSTEM,
|
||||
CoreStates,
|
||||
)
|
||||
from .const import AddonStartup, CoreStates
|
||||
from .coresys import CoreSys, CoreSysAttributes
|
||||
from .exceptions import HassioError, HomeAssistantError, SupervisorUpdateError
|
||||
|
||||
@ -133,7 +127,7 @@ class Core(CoreSysAttributes):
|
||||
)
|
||||
|
||||
# Start addon mark as initialize
|
||||
await self.sys_addons.boot(STARTUP_INITIALIZE)
|
||||
await self.sys_addons.boot(AddonStartup.INITIALIZE)
|
||||
|
||||
try:
|
||||
# HomeAssistant is already running / supervisor have only reboot
|
||||
@ -145,10 +139,10 @@ class Core(CoreSysAttributes):
|
||||
self.sys_services.reset()
|
||||
|
||||
# start addon mark as system
|
||||
await self.sys_addons.boot(STARTUP_SYSTEM)
|
||||
await self.sys_addons.boot(AddonStartup.SYSTEM)
|
||||
|
||||
# start addon mark as services
|
||||
await self.sys_addons.boot(STARTUP_SERVICES)
|
||||
await self.sys_addons.boot(AddonStartup.SERVICES)
|
||||
|
||||
# run HomeAssistant
|
||||
if (
|
||||
@ -161,7 +155,7 @@ class Core(CoreSysAttributes):
|
||||
_LOGGER.info("Skip start of Home Assistant")
|
||||
|
||||
# start addon mark as application
|
||||
await self.sys_addons.boot(STARTUP_APPLICATION)
|
||||
await self.sys_addons.boot(AddonStartup.APPLICATION)
|
||||
|
||||
# store new last boot
|
||||
self._update_last_boot()
|
||||
@ -214,22 +208,24 @@ class Core(CoreSysAttributes):
|
||||
async def shutdown(self):
|
||||
"""Shutdown all running containers in correct order."""
|
||||
# don't process scheduler anymore
|
||||
self.state = CoreStates.STOPPING
|
||||
if self.state == CoreStates.RUNNING:
|
||||
self.state = CoreStates.STOPPING
|
||||
|
||||
# Shutdown Application Add-ons, using Home Assistant API
|
||||
await self.sys_addons.shutdown(STARTUP_APPLICATION)
|
||||
await self.sys_addons.shutdown(AddonStartup.APPLICATION)
|
||||
|
||||
# Close Home Assistant
|
||||
with suppress(HassioError):
|
||||
await self.sys_homeassistant.stop()
|
||||
|
||||
# Shutdown System Add-ons
|
||||
await self.sys_addons.shutdown(STARTUP_SERVICES)
|
||||
await self.sys_addons.shutdown(STARTUP_SYSTEM)
|
||||
await self.sys_addons.shutdown(STARTUP_INITIALIZE)
|
||||
await self.sys_addons.shutdown(AddonStartup.SERVICES)
|
||||
await self.sys_addons.shutdown(AddonStartup.SYSTEM)
|
||||
await self.sys_addons.shutdown(AddonStartup.INITIALIZE)
|
||||
|
||||
# Shutdown all Plugins
|
||||
await self.sys_plugins.shutdown()
|
||||
if self.state == CoreStates.STOPPING:
|
||||
await self.sys_plugins.shutdown()
|
||||
|
||||
def _update_last_boot(self):
|
||||
"""Update last boot time."""
|
||||
|
@ -118,7 +118,7 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
||||
async def _push_discovery(self, message: Message, command: str) -> None:
|
||||
"""Send a discovery request."""
|
||||
if not await self.sys_homeassistant.check_api_state():
|
||||
_LOGGER.info("Discovery %s mesage ignore", message.uuid)
|
||||
_LOGGER.info("Discovery %s message ignore", message.uuid)
|
||||
return
|
||||
|
||||
data = attr.asdict(message)
|
||||
|
@ -97,7 +97,7 @@ class HassOS(CoreSysAttributes):
|
||||
if cpe.get_product()[0] != "hassos":
|
||||
raise NotImplementedError()
|
||||
except NotImplementedError:
|
||||
_LOGGER.warning("No Home Assistant Operating-System found!")
|
||||
_LOGGER.warning("No Home Assistant Operating System found!")
|
||||
return
|
||||
else:
|
||||
self._available = True
|
||||
|
@ -374,6 +374,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
await self.instance.run()
|
||||
except DockerAPIError:
|
||||
raise HomeAssistantError() from None
|
||||
|
||||
# Don't block for landingpage
|
||||
if self.version == "landingpage":
|
||||
return
|
||||
await self._block_till_run()
|
||||
|
||||
@process_lock
|
||||
@ -559,12 +563,21 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
|
||||
async def check_api_state(self) -> bool:
|
||||
"""Return True if Home Assistant up and running."""
|
||||
# Check if port is up
|
||||
if not await self.sys_run_in_executor(
|
||||
check_port, self.ip_address, self.api_port
|
||||
):
|
||||
return False
|
||||
|
||||
# Check if API is up
|
||||
with suppress(HomeAssistantAPIError):
|
||||
async with self.make_request("get", "api/") as resp:
|
||||
async with self.make_request("get", "api/config") as resp:
|
||||
if resp.status in (200, 201):
|
||||
return True
|
||||
status = resp.status
|
||||
_LOGGER.warning("Home Assistant API config mismatch: %s", status)
|
||||
data = await resp.json()
|
||||
if data.get("state", "RUNNING") == "RUNNING":
|
||||
return True
|
||||
else:
|
||||
_LOGGER.debug("Home Assistant API return: %d", resp.status)
|
||||
|
||||
return False
|
||||
|
||||
@ -589,9 +602,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
break
|
||||
|
||||
# 2: Check if API response
|
||||
if await self.sys_run_in_executor(
|
||||
check_port, self.ip_address, self.api_port
|
||||
):
|
||||
if await self.check_api_state():
|
||||
_LOGGER.info("Detect a running Home Assistant instance")
|
||||
self._error_state = False
|
||||
return
|
||||
|
@ -1,9 +1,9 @@
|
||||
"""A collection of tasks."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..exceptions import (
|
||||
AddonsError,
|
||||
AudioError,
|
||||
CliError,
|
||||
CoreDNSError,
|
||||
@ -131,24 +131,26 @@ class Tasks(CoreSysAttributes):
|
||||
|
||||
async def _update_addons(self):
|
||||
"""Check if an update is available for an Add-on and update it."""
|
||||
tasks = []
|
||||
for addon in self.sys_addons.all:
|
||||
if not addon.is_installed or not addon.auto_update:
|
||||
continue
|
||||
|
||||
# Evaluate available updates
|
||||
if addon.version == addon.latest_version:
|
||||
continue
|
||||
|
||||
if addon.test_update_schema():
|
||||
tasks.append(addon.update())
|
||||
else:
|
||||
if not addon.test_update_schema():
|
||||
_LOGGER.warning(
|
||||
"Add-on %s will be ignored, schema tests fails", addon.slug
|
||||
)
|
||||
continue
|
||||
|
||||
if tasks:
|
||||
_LOGGER.info("Add-on auto update process %d tasks", len(tasks))
|
||||
await asyncio.wait(tasks)
|
||||
# Run Add-on update sequential
|
||||
# avoid issue on slow IO
|
||||
_LOGGER.info("Add-on auto update process %s", addon.slug)
|
||||
try:
|
||||
await addon.update()
|
||||
except AddonsError:
|
||||
_LOGGER.error("Can't auto update Add-on %s", addon.slug)
|
||||
|
||||
async def _update_supervisor(self):
|
||||
"""Check and run update of Supervisor Supervisor."""
|
||||
|
@ -51,9 +51,17 @@ class PluginManager(CoreSysAttributes):
|
||||
|
||||
async def load(self):
|
||||
"""Load Supervisor plugins."""
|
||||
await asyncio.wait(
|
||||
[self.dns.load(), self.audio.load(), self.cli.load(), self.multicast.load()]
|
||||
)
|
||||
# Sequential to avoid issue on slow IO
|
||||
for plugin in (
|
||||
self.dns,
|
||||
self.audio,
|
||||
self.cli,
|
||||
self.multicast,
|
||||
):
|
||||
try:
|
||||
await plugin.load()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.warning("Can't load plugin %s", type(plugin).__name__)
|
||||
|
||||
# Check requirements
|
||||
for plugin, required_version in (
|
||||
@ -103,11 +111,14 @@ class PluginManager(CoreSysAttributes):
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""Shutdown Supervisor plugin."""
|
||||
await asyncio.wait(
|
||||
[
|
||||
self.dns.stop(),
|
||||
self.audio.stop(),
|
||||
self.cli.stop(),
|
||||
self.multicast.stop(),
|
||||
]
|
||||
)
|
||||
# Sequential to avoid issue on slow IO
|
||||
for plugin in (
|
||||
self.audio,
|
||||
self.cli,
|
||||
self.multicast,
|
||||
self.dns,
|
||||
):
|
||||
try:
|
||||
await plugin.stop()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.warning("Can't stop plugin %s", type(plugin).__name__)
|
||||
|
Loading…
x
Reference in New Issue
Block a user