mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-15 13:16:29 +00:00
Group all homeassistant/core into a module (#1950)
* Group all homeassistant/core into a module * fix references * fix lint * streamline object property protection * Fix api
This commit is contained in:
parent
b3308ecbe0
commit
2411b4287d
@ -359,7 +359,7 @@ class Addon(AddonModel):
|
||||
options = self.options
|
||||
|
||||
# Update secrets for validation
|
||||
await self.sys_secrets.reload()
|
||||
await self.sys_homeassistant.secrets.reload()
|
||||
|
||||
try:
|
||||
options = schema(options)
|
||||
|
@ -282,7 +282,7 @@ class APIAddons(CoreSysAttributes):
|
||||
addon = self._extract_addon_installed(request)
|
||||
|
||||
# Update secrets for validation
|
||||
await self.sys_secrets.reload()
|
||||
await self.sys_homeassistant.secrets.reload()
|
||||
|
||||
# Extend schema with add-on specific validation
|
||||
addon_schema = SCHEMA_OPTIONS.extend(
|
||||
|
@ -117,7 +117,7 @@ class APIHomeAssistant(CoreSysAttributes):
|
||||
@api_process
|
||||
async def stats(self, request: web.Request) -> Dict[Any, str]:
|
||||
"""Return resource information."""
|
||||
stats = await self.sys_homeassistant.stats()
|
||||
stats = await self.sys_homeassistant.core.stats()
|
||||
if not stats:
|
||||
raise APIError("No stats available")
|
||||
|
||||
@ -138,36 +138,36 @@ class APIHomeAssistant(CoreSysAttributes):
|
||||
body = await api_validate(SCHEMA_VERSION, request)
|
||||
version = body.get(ATTR_VERSION, self.sys_homeassistant.latest_version)
|
||||
|
||||
await asyncio.shield(self.sys_homeassistant.update(version))
|
||||
await asyncio.shield(self.sys_homeassistant.core.update(version))
|
||||
|
||||
@api_process
|
||||
def stop(self, request: web.Request) -> Awaitable[None]:
|
||||
"""Stop Home Assistant."""
|
||||
return asyncio.shield(self.sys_homeassistant.stop())
|
||||
return asyncio.shield(self.sys_homeassistant.core.stop())
|
||||
|
||||
@api_process
|
||||
def start(self, request: web.Request) -> Awaitable[None]:
|
||||
"""Start Home Assistant."""
|
||||
return asyncio.shield(self.sys_homeassistant.start())
|
||||
return asyncio.shield(self.sys_homeassistant.core.start())
|
||||
|
||||
@api_process
|
||||
def restart(self, request: web.Request) -> Awaitable[None]:
|
||||
"""Restart Home Assistant."""
|
||||
return asyncio.shield(self.sys_homeassistant.restart())
|
||||
return asyncio.shield(self.sys_homeassistant.core.restart())
|
||||
|
||||
@api_process
|
||||
def rebuild(self, request: web.Request) -> Awaitable[None]:
|
||||
"""Rebuild Home Assistant."""
|
||||
return asyncio.shield(self.sys_homeassistant.rebuild())
|
||||
return asyncio.shield(self.sys_homeassistant.core.rebuild())
|
||||
|
||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
||||
"""Return Home Assistant Docker logs."""
|
||||
return self.sys_homeassistant.logs()
|
||||
return self.sys_homeassistant.core.logs()
|
||||
|
||||
@api_process
|
||||
async def check(self, request: web.Request) -> None:
|
||||
"""Check configuration of Home Assistant."""
|
||||
result = await self.sys_homeassistant.check_config()
|
||||
result = await self.sys_homeassistant.core.check_config()
|
||||
if not result.valid:
|
||||
raise APIError(result.log)
|
||||
|
@ -45,7 +45,7 @@ class APIProxy(CoreSysAttributes):
|
||||
async def _api_client(self, request: web.Request, path: str, timeout: int = 300):
|
||||
"""Return a client request with proxy origin for Home Assistant."""
|
||||
try:
|
||||
async with self.sys_homeassistant.make_request(
|
||||
async with self.sys_homeassistant.api.make_request(
|
||||
request.method.lower(),
|
||||
f"api/{path}",
|
||||
headers={
|
||||
@ -75,7 +75,7 @@ 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():
|
||||
if not await self.sys_homeassistant.api.check_api_state():
|
||||
raise HTTPBadGateway()
|
||||
|
||||
_LOGGER.info("Home Assistant EventStream start")
|
||||
@ -96,7 +96,7 @@ 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():
|
||||
if not await self.sys_homeassistant.api.check_api_state():
|
||||
raise HTTPBadGateway()
|
||||
|
||||
# Normal request
|
||||
@ -130,7 +130,10 @@ class APIProxy(CoreSysAttributes):
|
||||
# Auth session
|
||||
await self.sys_homeassistant.ensure_access_token()
|
||||
await client.send_json(
|
||||
{"type": "auth", "access_token": self.sys_homeassistant.access_token}
|
||||
{
|
||||
"type": "auth",
|
||||
"access_token": self.sys_homeassistant.api.access_token,
|
||||
}
|
||||
)
|
||||
|
||||
data = await client.receive_json()
|
||||
@ -143,7 +146,7 @@ class APIProxy(CoreSysAttributes):
|
||||
data.get("type") == "invalid_auth"
|
||||
and self.sys_homeassistant.refresh_token
|
||||
):
|
||||
self.sys_homeassistant.access_token = None
|
||||
self.sys_homeassistant.api.access_token = None
|
||||
return await self._websocket_client()
|
||||
|
||||
raise HomeAssistantAuthError()
|
||||
@ -157,7 +160,7 @@ class APIProxy(CoreSysAttributes):
|
||||
|
||||
async def websocket(self, request: web.Request):
|
||||
"""Initialize a WebSocket API connection."""
|
||||
if not await self.sys_homeassistant.check_api_state():
|
||||
if not await self.sys_homeassistant.api.check_api_state():
|
||||
raise HTTPBadGateway()
|
||||
_LOGGER.info("Home Assistant WebSocket API request initialize")
|
||||
|
||||
|
@ -176,7 +176,9 @@ class APISupervisor(CoreSysAttributes):
|
||||
def reload(self, request: web.Request) -> Awaitable[None]:
|
||||
"""Reload add-ons, configuration, etc."""
|
||||
return asyncio.shield(
|
||||
asyncio.wait([self.sys_updater.reload(), self.sys_secrets.reload()])
|
||||
asyncio.wait(
|
||||
[self.sys_updater.reload(), self.sys_homeassistant.secrets.reload()]
|
||||
)
|
||||
)
|
||||
|
||||
@api_process
|
||||
|
@ -62,12 +62,12 @@ class Auth(JsonConfig, CoreSysAttributes):
|
||||
_LOGGER.info("Auth request from %s for %s", addon.slug, username)
|
||||
|
||||
# Check API state
|
||||
if not await self.sys_homeassistant.check_api_state():
|
||||
if not await self.sys_homeassistant.api.check_api_state():
|
||||
_LOGGER.info("Home Assistant not running, check cache")
|
||||
return self._check_cache(username, password)
|
||||
|
||||
try:
|
||||
async with self.sys_homeassistant.make_request(
|
||||
async with self.sys_homeassistant.api.make_request(
|
||||
"post",
|
||||
"api/hassio_auth",
|
||||
json={
|
||||
@ -93,7 +93,7 @@ class Auth(JsonConfig, CoreSysAttributes):
|
||||
async def change_password(self, username: str, password: str) -> None:
|
||||
"""Change user password login."""
|
||||
try:
|
||||
async with self.sys_homeassistant.make_request(
|
||||
async with self.sys_homeassistant.api.make_request(
|
||||
"post",
|
||||
"api/hassio_auth/password_reset",
|
||||
json={ATTR_USERNAME: username, ATTR_PASSWORD: password},
|
||||
|
@ -37,7 +37,6 @@ from .ingress import Ingress
|
||||
from .misc.filter import filter_data
|
||||
from .misc.hwmon import HwMonitor
|
||||
from .misc.scheduler import Scheduler
|
||||
from .misc.secrets import SecretsManager
|
||||
from .misc.tasks import Tasks
|
||||
from .plugins import PluginManager
|
||||
from .services import ServiceManager
|
||||
@ -74,7 +73,6 @@ async def initialize_coresys() -> CoreSys:
|
||||
coresys.discovery = Discovery(coresys)
|
||||
coresys.dbus = DBusManager(coresys)
|
||||
coresys.hassos = HassOS(coresys)
|
||||
coresys.secrets = SecretsManager(coresys)
|
||||
coresys.scheduler = Scheduler(coresys)
|
||||
|
||||
# diagnostics
|
||||
|
@ -122,9 +122,6 @@ class Core(CoreSysAttributes):
|
||||
# Load ingress
|
||||
await self.sys_ingress.load()
|
||||
|
||||
# Load secrets
|
||||
await self.sys_secrets.load()
|
||||
|
||||
# Check supported OS
|
||||
if not self.sys_hassos.available:
|
||||
if self.sys_host.info.operating_system not in SUPERVISED_SUPPORTED_OS:
|
||||
@ -204,10 +201,10 @@ class Core(CoreSysAttributes):
|
||||
# run HomeAssistant
|
||||
if (
|
||||
self.sys_homeassistant.boot
|
||||
and not await self.sys_homeassistant.is_running()
|
||||
and not await self.sys_homeassistant.core.is_running()
|
||||
):
|
||||
with suppress(HomeAssistantError):
|
||||
await self.sys_homeassistant.start()
|
||||
await self.sys_homeassistant.core.start()
|
||||
else:
|
||||
_LOGGER.info("Skip start of Home Assistant")
|
||||
|
||||
@ -223,7 +220,7 @@ class Core(CoreSysAttributes):
|
||||
|
||||
# If landingpage / run upgrade in background
|
||||
if self.sys_homeassistant.version == "landingpage":
|
||||
self.sys_create_task(self.sys_homeassistant.install())
|
||||
self.sys_create_task(self.sys_homeassistant.core.install())
|
||||
|
||||
# Start observe the host Hardware
|
||||
await self.sys_hwmonitor.load()
|
||||
@ -278,7 +275,7 @@ class Core(CoreSysAttributes):
|
||||
|
||||
# Close Home Assistant
|
||||
with suppress(HassioError):
|
||||
await self.sys_homeassistant.stop()
|
||||
await self.sys_homeassistant.core.stop()
|
||||
|
||||
# Shutdown System Add-ons
|
||||
await self.sys_addons.shutdown(AddonStartup.SERVICES)
|
||||
@ -304,7 +301,7 @@ class Core(CoreSysAttributes):
|
||||
|
||||
# Restore core functionality
|
||||
await self.sys_addons.repair()
|
||||
await self.sys_homeassistant.repair()
|
||||
await self.sys_homeassistant.core.repair()
|
||||
|
||||
# Tag version for latest
|
||||
await self.sys_supervisor.repair()
|
||||
|
@ -23,7 +23,6 @@ if TYPE_CHECKING:
|
||||
from .hassos import HassOS
|
||||
from .misc.scheduler import Scheduler
|
||||
from .misc.hwmon import HwMonitor
|
||||
from .misc.secrets import SecretsManager
|
||||
from .misc.tasks import Tasks
|
||||
from .homeassistant import HomeAssistant
|
||||
from .host import HostManager
|
||||
@ -76,7 +75,6 @@ class CoreSys:
|
||||
self._dbus: Optional[DBusManager] = None
|
||||
self._hassos: Optional[HassOS] = None
|
||||
self._services: Optional[ServiceManager] = None
|
||||
self._secrets: Optional[SecretsManager] = None
|
||||
self._scheduler: Optional[Scheduler] = None
|
||||
self._store: Optional[StoreManager] = None
|
||||
self._discovery: Optional[Discovery] = None
|
||||
@ -246,20 +244,6 @@ class CoreSys:
|
||||
raise RuntimeError("Updater already set!")
|
||||
self._updater = value
|
||||
|
||||
@property
|
||||
def secrets(self) -> SecretsManager:
|
||||
"""Return SecretsManager object."""
|
||||
if self._secrets is None:
|
||||
raise RuntimeError("SecretsManager not set!")
|
||||
return self._secrets
|
||||
|
||||
@secrets.setter
|
||||
def secrets(self, value: SecretsManager) -> None:
|
||||
"""Set a Updater object."""
|
||||
if self._secrets:
|
||||
raise RuntimeError("SecretsManager already set!")
|
||||
self._secrets = value
|
||||
|
||||
@property
|
||||
def addons(self) -> AddonManager:
|
||||
"""Return AddonManager object."""
|
||||
@ -529,11 +513,6 @@ class CoreSysAttributes:
|
||||
"""Return Updater object."""
|
||||
return self.coresys.updater
|
||||
|
||||
@property
|
||||
def sys_secrets(self) -> SecretsManager:
|
||||
"""Return SecretsManager object."""
|
||||
return self.coresys.secrets
|
||||
|
||||
@property
|
||||
def sys_addons(self) -> AddonManager:
|
||||
"""Return AddonManager object."""
|
||||
|
@ -117,7 +117,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():
|
||||
if not await self.sys_homeassistant.api.check_api_state():
|
||||
_LOGGER.info("Discovery %s message ignore", message.uuid)
|
||||
return
|
||||
|
||||
@ -125,7 +125,7 @@ class Discovery(CoreSysAttributes, JsonConfig):
|
||||
data.pop(ATTR_CONFIG)
|
||||
|
||||
with suppress(HomeAssistantAPIError):
|
||||
async with self.sys_homeassistant.make_request(
|
||||
async with self.sys_homeassistant.api.make_request(
|
||||
command,
|
||||
f"api/hassio_push/discovery/{message.uuid}",
|
||||
json=data,
|
||||
|
241
supervisor/homeassistant/__init__.py
Normal file
241
supervisor/homeassistant/__init__.py
Normal file
@ -0,0 +1,241 @@
|
||||
"""Home Assistant control object."""
|
||||
import asyncio
|
||||
from ipaddress import IPv4Address
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from ..const import (
|
||||
ATTR_ACCESS_TOKEN,
|
||||
ATTR_AUDIO_INPUT,
|
||||
ATTR_AUDIO_OUTPUT,
|
||||
ATTR_BOOT,
|
||||
ATTR_IMAGE,
|
||||
ATTR_PORT,
|
||||
ATTR_REFRESH_TOKEN,
|
||||
ATTR_SSL,
|
||||
ATTR_UUID,
|
||||
ATTR_VERSION,
|
||||
ATTR_WAIT_BOOT,
|
||||
ATTR_WATCHDOG,
|
||||
FILE_HASSIO_HOMEASSISTANT,
|
||||
)
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..utils.json import JsonConfig
|
||||
from ..validate import SCHEMA_HASS_CONFIG
|
||||
from .api import HomeAssistantAPI
|
||||
from .core import HomeAssistantCore
|
||||
from .secrets import HomeAssistantSecrets
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
"""Home Assistant core object for handle it."""
|
||||
|
||||
def __init__(self, coresys: CoreSys):
|
||||
"""Initialize Home Assistant object."""
|
||||
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
|
||||
self.coresys: CoreSys = coresys
|
||||
self._api: HomeAssistantAPI = HomeAssistantAPI(coresys)
|
||||
self._core: HomeAssistantCore = HomeAssistantCore(coresys)
|
||||
self._secrets: HomeAssistantSecrets = HomeAssistantSecrets(coresys)
|
||||
|
||||
@property
|
||||
def api(self) -> HomeAssistantAPI:
|
||||
"""Return API handler for core."""
|
||||
return self._api
|
||||
|
||||
@property
|
||||
def core(self) -> HomeAssistantCore:
|
||||
"""Return Core handler for docker."""
|
||||
return self._core
|
||||
|
||||
@property
|
||||
def secrets(self) -> HomeAssistantSecrets:
|
||||
"""Return Secrets Manager for core."""
|
||||
return self._secrets
|
||||
|
||||
@property
|
||||
def machine(self) -> str:
|
||||
"""Return the system machines."""
|
||||
return self.core.instance.machine
|
||||
|
||||
@property
|
||||
def arch(self) -> str:
|
||||
"""Return arch of running Home Assistant."""
|
||||
return self.core.instance.arch
|
||||
|
||||
@property
|
||||
def error_state(self) -> bool:
|
||||
"""Return True if system is in error."""
|
||||
return self.core.error_state
|
||||
|
||||
@property
|
||||
def ip_address(self) -> IPv4Address:
|
||||
"""Return IP of Home Assistant instance."""
|
||||
return self.core.instance.ip_address
|
||||
|
||||
@property
|
||||
def api_port(self) -> int:
|
||||
"""Return network port to Home Assistant instance."""
|
||||
return self._data[ATTR_PORT]
|
||||
|
||||
@api_port.setter
|
||||
def api_port(self, value: int) -> None:
|
||||
"""Set network port for Home Assistant instance."""
|
||||
self._data[ATTR_PORT] = value
|
||||
|
||||
@property
|
||||
def api_ssl(self) -> bool:
|
||||
"""Return if we need ssl to Home Assistant instance."""
|
||||
return self._data[ATTR_SSL]
|
||||
|
||||
@api_ssl.setter
|
||||
def api_ssl(self, value: bool):
|
||||
"""Set SSL for Home Assistant instance."""
|
||||
self._data[ATTR_SSL] = value
|
||||
|
||||
@property
|
||||
def api_url(self) -> str:
|
||||
"""Return API url to Home Assistant."""
|
||||
return "{}://{}:{}".format(
|
||||
"https" if self.api_ssl else "http", self.ip_address, self.api_port
|
||||
)
|
||||
|
||||
@property
|
||||
def watchdog(self) -> bool:
|
||||
"""Return True if the watchdog should protect Home Assistant."""
|
||||
return self._data[ATTR_WATCHDOG]
|
||||
|
||||
@watchdog.setter
|
||||
def watchdog(self, value: bool):
|
||||
"""Return True if the watchdog should protect Home Assistant."""
|
||||
self._data[ATTR_WATCHDOG] = value
|
||||
|
||||
@property
|
||||
def wait_boot(self) -> int:
|
||||
"""Return time to wait for Home Assistant startup."""
|
||||
return self._data[ATTR_WAIT_BOOT]
|
||||
|
||||
@wait_boot.setter
|
||||
def wait_boot(self, value: int):
|
||||
"""Set time to wait for Home Assistant startup."""
|
||||
self._data[ATTR_WAIT_BOOT] = value
|
||||
|
||||
@property
|
||||
def latest_version(self) -> str:
|
||||
"""Return last available version of Home Assistant."""
|
||||
return self.sys_updater.version_homeassistant
|
||||
|
||||
@property
|
||||
def image(self) -> str:
|
||||
"""Return image name of the Home Assistant container."""
|
||||
if self._data.get(ATTR_IMAGE):
|
||||
return self._data[ATTR_IMAGE]
|
||||
return f"homeassistant/{self.sys_machine}-homeassistant"
|
||||
|
||||
@image.setter
|
||||
def image(self, value: str) -> None:
|
||||
"""Set image name of Home Assistant container."""
|
||||
self._data[ATTR_IMAGE] = value
|
||||
|
||||
@property
|
||||
def version(self) -> Optional[str]:
|
||||
"""Return version of local version."""
|
||||
return self._data.get(ATTR_VERSION)
|
||||
|
||||
@version.setter
|
||||
def version(self, value: str) -> None:
|
||||
"""Set installed version."""
|
||||
self._data[ATTR_VERSION] = value
|
||||
|
||||
@property
|
||||
def boot(self) -> bool:
|
||||
"""Return True if Home Assistant boot is enabled."""
|
||||
return self._data[ATTR_BOOT]
|
||||
|
||||
@boot.setter
|
||||
def boot(self, value: bool):
|
||||
"""Set Home Assistant boot options."""
|
||||
self._data[ATTR_BOOT] = value
|
||||
|
||||
@property
|
||||
def uuid(self) -> UUID:
|
||||
"""Return a UUID of this Home Assistant instance."""
|
||||
return self._data[ATTR_UUID]
|
||||
|
||||
@property
|
||||
def supervisor_token(self) -> Optional[str]:
|
||||
"""Return an access token for the Supervisor API."""
|
||||
return self._data.get(ATTR_ACCESS_TOKEN)
|
||||
|
||||
@supervisor_token.setter
|
||||
def supervisor_token(self, value: str) -> None:
|
||||
"""Set the access token for the Supervisor API."""
|
||||
self._data[ATTR_ACCESS_TOKEN] = value
|
||||
|
||||
@property
|
||||
def refresh_token(self) -> Optional[str]:
|
||||
"""Return the refresh token to authenticate with Home Assistant."""
|
||||
return self._data.get(ATTR_REFRESH_TOKEN)
|
||||
|
||||
@refresh_token.setter
|
||||
def refresh_token(self, value: str):
|
||||
"""Set Home Assistant refresh_token."""
|
||||
self._data[ATTR_REFRESH_TOKEN] = value
|
||||
|
||||
@property
|
||||
def path_pulse(self):
|
||||
"""Return path to asound config."""
|
||||
return Path(self.sys_config.path_tmp, "homeassistant_pulse")
|
||||
|
||||
@property
|
||||
def path_extern_pulse(self):
|
||||
"""Return path to asound config for Docker."""
|
||||
return Path(self.sys_config.path_extern_tmp, "homeassistant_pulse")
|
||||
|
||||
@property
|
||||
def audio_output(self) -> Optional[str]:
|
||||
"""Return a pulse profile for output or None."""
|
||||
return self._data[ATTR_AUDIO_OUTPUT]
|
||||
|
||||
@audio_output.setter
|
||||
def audio_output(self, value: Optional[str]):
|
||||
"""Set audio output profile settings."""
|
||||
self._data[ATTR_AUDIO_OUTPUT] = value
|
||||
|
||||
@property
|
||||
def audio_input(self) -> Optional[str]:
|
||||
"""Return pulse profile for input or None."""
|
||||
return self._data[ATTR_AUDIO_INPUT]
|
||||
|
||||
@audio_input.setter
|
||||
def audio_input(self, value: Optional[str]):
|
||||
"""Set audio input settings."""
|
||||
self._data[ATTR_AUDIO_INPUT] = value
|
||||
|
||||
async def load(self) -> None:
|
||||
"""Prepare Home Assistant object."""
|
||||
await asyncio.wait([self.secrets.load(), self.core.load()])
|
||||
|
||||
def write_pulse(self):
|
||||
"""Write asound config to file and return True on success."""
|
||||
pulse_config = self.sys_plugins.audio.pulse_client(
|
||||
input_profile=self.audio_input, output_profile=self.audio_output
|
||||
)
|
||||
|
||||
# Cleanup wrong maps
|
||||
if self.path_pulse.is_dir():
|
||||
shutil.rmtree(self.path_pulse, ignore_errors=True)
|
||||
|
||||
# Write pulse config
|
||||
try:
|
||||
with self.path_pulse.open("w") as config_file:
|
||||
config_file.write(pulse_config)
|
||||
except OSError as err:
|
||||
_LOGGER.error("Home Assistant can't write pulse/client.config: %s", err)
|
||||
else:
|
||||
_LOGGER.info("Update pulse/client.config: %s", self.path_pulse)
|
122
supervisor/homeassistant/api.py
Normal file
122
supervisor/homeassistant/api.py
Normal file
@ -0,0 +1,122 @@
|
||||
"""Home Assistant control object."""
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager, suppress
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Any, AsyncContextManager, Dict, Optional
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import hdrs
|
||||
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import HomeAssistantAPIError, HomeAssistantAuthError
|
||||
from ..utils import check_port
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HomeAssistantAPI(CoreSysAttributes):
|
||||
"""Home Assistant core object for handle it."""
|
||||
|
||||
def __init__(self, coresys: CoreSys):
|
||||
"""Initialize Home Assistant object."""
|
||||
self.coresys: CoreSys = coresys
|
||||
|
||||
# We don't persist access tokens. Instead we fetch new ones when needed
|
||||
self.access_token: Optional[str] = None
|
||||
self._access_token_expires: Optional[datetime] = None
|
||||
|
||||
async def _ensure_access_token(self) -> None:
|
||||
"""Ensure there is an access token."""
|
||||
if (
|
||||
self.access_token is not None
|
||||
and self._access_token_expires > datetime.utcnow()
|
||||
):
|
||||
return
|
||||
|
||||
with suppress(asyncio.TimeoutError, aiohttp.ClientError):
|
||||
async with self.sys_websession_ssl.post(
|
||||
f"{self.sys_homeassistant.api_url}/auth/token",
|
||||
timeout=30,
|
||||
data={
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": self.sys_homeassistant.refresh_token,
|
||||
},
|
||||
) as resp:
|
||||
if resp.status != 200:
|
||||
_LOGGER.error("Can't update Home Assistant access token!")
|
||||
raise HomeAssistantAuthError()
|
||||
|
||||
_LOGGER.info("Updated Home Assistant API token")
|
||||
tokens = await resp.json()
|
||||
self.access_token = tokens["access_token"]
|
||||
self._access_token_expires = datetime.utcnow() + timedelta(
|
||||
seconds=tokens["expires_in"]
|
||||
)
|
||||
|
||||
@asynccontextmanager
|
||||
async def make_request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
json: Optional[Dict[str, Any]] = None,
|
||||
content_type: Optional[str] = None,
|
||||
data: Any = None,
|
||||
timeout: int = 30,
|
||||
params: Optional[Dict[str, str]] = None,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
) -> AsyncContextManager[aiohttp.ClientResponse]:
|
||||
"""Async context manager to make a request with right auth."""
|
||||
url = f"{self.sys_homeassistant.api_url}/{path}"
|
||||
headers = headers or {}
|
||||
|
||||
# Passthrough content type
|
||||
if content_type is not None:
|
||||
headers[hdrs.CONTENT_TYPE] = content_type
|
||||
|
||||
for _ in (1, 2):
|
||||
await self._ensure_access_token()
|
||||
headers[hdrs.AUTHORIZATION] = f"Bearer {self.access_token}"
|
||||
|
||||
try:
|
||||
async with getattr(self.sys_websession_ssl, method)(
|
||||
url,
|
||||
data=data,
|
||||
timeout=timeout,
|
||||
json=json,
|
||||
headers=headers,
|
||||
params=params,
|
||||
) as resp:
|
||||
# Access token expired
|
||||
if resp.status == 401:
|
||||
self.access_token = None
|
||||
continue
|
||||
yield resp
|
||||
return
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
|
||||
_LOGGER.error("Error on call %s: %s", url, err)
|
||||
break
|
||||
|
||||
raise HomeAssistantAPIError()
|
||||
|
||||
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.sys_homeassistant.ip_address,
|
||||
self.sys_homeassistant.api_port,
|
||||
):
|
||||
return False
|
||||
|
||||
# Check if API is up
|
||||
with suppress(HomeAssistantAPIError):
|
||||
async with self.make_request("get", "api/config") as resp:
|
||||
if resp.status in (200, 201):
|
||||
data = await resp.json()
|
||||
if data.get("state", "RUNNING") == "RUNNING":
|
||||
return True
|
||||
else:
|
||||
_LOGGER.debug("Home Assistant API return: %d", resp.status)
|
||||
|
||||
return False
|
@ -1,50 +1,22 @@
|
||||
"""Home Assistant control object."""
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager, suppress
|
||||
from datetime import datetime, timedelta
|
||||
from ipaddress import IPv4Address
|
||||
from contextlib import suppress
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
import secrets
|
||||
import shutil
|
||||
import time
|
||||
from typing import Any, AsyncContextManager, Awaitable, Dict, Optional
|
||||
from uuid import UUID
|
||||
from typing import Awaitable, Optional
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import hdrs
|
||||
import attr
|
||||
from packaging import version as pkg_version
|
||||
|
||||
from .const import (
|
||||
ATTR_ACCESS_TOKEN,
|
||||
ATTR_AUDIO_INPUT,
|
||||
ATTR_AUDIO_OUTPUT,
|
||||
ATTR_BOOT,
|
||||
ATTR_IMAGE,
|
||||
ATTR_PORT,
|
||||
ATTR_REFRESH_TOKEN,
|
||||
ATTR_SSL,
|
||||
ATTR_UUID,
|
||||
ATTR_VERSION,
|
||||
ATTR_WAIT_BOOT,
|
||||
ATTR_WATCHDOG,
|
||||
FILE_HASSIO_HOMEASSISTANT,
|
||||
)
|
||||
from .coresys import CoreSys, CoreSysAttributes
|
||||
from .docker.homeassistant import DockerHomeAssistant
|
||||
from .docker.stats import DockerStats
|
||||
from .exceptions import (
|
||||
DockerAPIError,
|
||||
HomeAssistantAPIError,
|
||||
HomeAssistantAuthError,
|
||||
HomeAssistantError,
|
||||
HomeAssistantUpdateError,
|
||||
)
|
||||
from .utils import check_port, convert_to_ascii, process_lock
|
||||
from .utils.json import JsonConfig
|
||||
from .validate import SCHEMA_HASS_CONFIG
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..docker.homeassistant import DockerHomeAssistant
|
||||
from ..docker.stats import DockerStats
|
||||
from ..exceptions import DockerAPIError, HomeAssistantError, HomeAssistantUpdateError
|
||||
from ..utils import convert_to_ascii, process_lock
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@ -61,192 +33,40 @@ class ConfigResult:
|
||||
log = attr.ib()
|
||||
|
||||
|
||||
class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
class HomeAssistantCore(CoreSysAttributes):
|
||||
"""Home Assistant core object for handle it."""
|
||||
|
||||
def __init__(self, coresys: CoreSys):
|
||||
"""Initialize Home Assistant object."""
|
||||
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
|
||||
self.coresys: CoreSys = coresys
|
||||
self.instance: DockerHomeAssistant = DockerHomeAssistant(coresys)
|
||||
self.lock: asyncio.Lock = asyncio.Lock()
|
||||
self._error_state: bool = False
|
||||
|
||||
# We don't persist access tokens. Instead we fetch new ones when needed
|
||||
self.access_token: Optional[str] = None
|
||||
self._access_token_expires: Optional[datetime] = None
|
||||
|
||||
async def load(self) -> None:
|
||||
"""Prepare Home Assistant object."""
|
||||
try:
|
||||
# Evaluate Version if we lost this information
|
||||
if not self.version:
|
||||
self.version = await self.instance.get_latest_version(
|
||||
key=pkg_version.parse
|
||||
)
|
||||
|
||||
await self.instance.attach(tag=self.version)
|
||||
except DockerAPIError:
|
||||
_LOGGER.info("No Home Assistant Docker image %s found.", self.image)
|
||||
await self.install_landingpage()
|
||||
else:
|
||||
self.version = self.instance.version
|
||||
self.image = self.instance.image
|
||||
self.save_data()
|
||||
|
||||
@property
|
||||
def machine(self) -> str:
|
||||
"""Return the system machines."""
|
||||
return self.instance.machine
|
||||
|
||||
@property
|
||||
def arch(self) -> str:
|
||||
"""Return arch of running Home Assistant."""
|
||||
return self.instance.arch
|
||||
|
||||
@property
|
||||
def error_state(self) -> bool:
|
||||
"""Return True if system is in error."""
|
||||
return self._error_state
|
||||
|
||||
@property
|
||||
def ip_address(self) -> IPv4Address:
|
||||
"""Return IP of Home Assistant instance."""
|
||||
return self.instance.ip_address
|
||||
async def load(self) -> None:
|
||||
"""Prepare Home Assistant object."""
|
||||
try:
|
||||
# Evaluate Version if we lost this information
|
||||
if not self.sys_homeassistant.version:
|
||||
self.sys_homeassistant.version = await self.instance.get_latest_version(
|
||||
key=pkg_version.parse
|
||||
)
|
||||
|
||||
@property
|
||||
def api_port(self) -> int:
|
||||
"""Return network port to Home Assistant instance."""
|
||||
return self._data[ATTR_PORT]
|
||||
|
||||
@api_port.setter
|
||||
def api_port(self, value: int) -> None:
|
||||
"""Set network port for Home Assistant instance."""
|
||||
self._data[ATTR_PORT] = value
|
||||
|
||||
@property
|
||||
def api_ssl(self) -> bool:
|
||||
"""Return if we need ssl to Home Assistant instance."""
|
||||
return self._data[ATTR_SSL]
|
||||
|
||||
@api_ssl.setter
|
||||
def api_ssl(self, value: bool):
|
||||
"""Set SSL for Home Assistant instance."""
|
||||
self._data[ATTR_SSL] = value
|
||||
|
||||
@property
|
||||
def api_url(self) -> str:
|
||||
"""Return API url to Home Assistant."""
|
||||
return "{}://{}:{}".format(
|
||||
"https" if self.api_ssl else "http", self.ip_address, self.api_port
|
||||
)
|
||||
|
||||
@property
|
||||
def watchdog(self) -> bool:
|
||||
"""Return True if the watchdog should protect Home Assistant."""
|
||||
return self._data[ATTR_WATCHDOG]
|
||||
|
||||
@watchdog.setter
|
||||
def watchdog(self, value: bool):
|
||||
"""Return True if the watchdog should protect Home Assistant."""
|
||||
self._data[ATTR_WATCHDOG] = value
|
||||
|
||||
@property
|
||||
def wait_boot(self) -> int:
|
||||
"""Return time to wait for Home Assistant startup."""
|
||||
return self._data[ATTR_WAIT_BOOT]
|
||||
|
||||
@wait_boot.setter
|
||||
def wait_boot(self, value: int):
|
||||
"""Set time to wait for Home Assistant startup."""
|
||||
self._data[ATTR_WAIT_BOOT] = value
|
||||
|
||||
@property
|
||||
def latest_version(self) -> str:
|
||||
"""Return last available version of Home Assistant."""
|
||||
return self.sys_updater.version_homeassistant
|
||||
|
||||
@property
|
||||
def image(self) -> str:
|
||||
"""Return image name of the Home Assistant container."""
|
||||
if self._data.get(ATTR_IMAGE):
|
||||
return self._data[ATTR_IMAGE]
|
||||
return f"homeassistant/{self.sys_machine}-homeassistant"
|
||||
|
||||
@image.setter
|
||||
def image(self, value: str) -> None:
|
||||
"""Set image name of Home Assistant container."""
|
||||
self._data[ATTR_IMAGE] = value
|
||||
|
||||
@property
|
||||
def version(self) -> Optional[str]:
|
||||
"""Return version of local version."""
|
||||
return self._data.get(ATTR_VERSION)
|
||||
|
||||
@version.setter
|
||||
def version(self, value: str) -> None:
|
||||
"""Set installed version."""
|
||||
self._data[ATTR_VERSION] = value
|
||||
|
||||
@property
|
||||
def boot(self) -> bool:
|
||||
"""Return True if Home Assistant boot is enabled."""
|
||||
return self._data[ATTR_BOOT]
|
||||
|
||||
@boot.setter
|
||||
def boot(self, value: bool):
|
||||
"""Set Home Assistant boot options."""
|
||||
self._data[ATTR_BOOT] = value
|
||||
|
||||
@property
|
||||
def uuid(self) -> UUID:
|
||||
"""Return a UUID of this Home Assistant instance."""
|
||||
return self._data[ATTR_UUID]
|
||||
|
||||
@property
|
||||
def supervisor_token(self) -> Optional[str]:
|
||||
"""Return an access token for the Supervisor API."""
|
||||
return self._data.get(ATTR_ACCESS_TOKEN)
|
||||
|
||||
@property
|
||||
def refresh_token(self) -> Optional[str]:
|
||||
"""Return the refresh token to authenticate with Home Assistant."""
|
||||
return self._data.get(ATTR_REFRESH_TOKEN)
|
||||
|
||||
@refresh_token.setter
|
||||
def refresh_token(self, value: str):
|
||||
"""Set Home Assistant refresh_token."""
|
||||
self._data[ATTR_REFRESH_TOKEN] = value
|
||||
|
||||
@property
|
||||
def path_pulse(self):
|
||||
"""Return path to asound config."""
|
||||
return Path(self.sys_config.path_tmp, "homeassistant_pulse")
|
||||
|
||||
@property
|
||||
def path_extern_pulse(self):
|
||||
"""Return path to asound config for Docker."""
|
||||
return Path(self.sys_config.path_extern_tmp, "homeassistant_pulse")
|
||||
|
||||
@property
|
||||
def audio_output(self) -> Optional[str]:
|
||||
"""Return a pulse profile for output or None."""
|
||||
return self._data[ATTR_AUDIO_OUTPUT]
|
||||
|
||||
@audio_output.setter
|
||||
def audio_output(self, value: Optional[str]):
|
||||
"""Set audio output profile settings."""
|
||||
self._data[ATTR_AUDIO_OUTPUT] = value
|
||||
|
||||
@property
|
||||
def audio_input(self) -> Optional[str]:
|
||||
"""Return pulse profile for input or None."""
|
||||
return self._data[ATTR_AUDIO_INPUT]
|
||||
|
||||
@audio_input.setter
|
||||
def audio_input(self, value: Optional[str]):
|
||||
"""Set audio input settings."""
|
||||
self._data[ATTR_AUDIO_INPUT] = value
|
||||
await self.instance.attach(tag=self.sys_homeassistant.version)
|
||||
except DockerAPIError:
|
||||
_LOGGER.info(
|
||||
"No Home Assistant Docker image %s found.", self.sys_homeassistant.image
|
||||
)
|
||||
await self.install_landingpage()
|
||||
else:
|
||||
self.sys_homeassistant.version = self.instance.version
|
||||
self.sys_homeassistant.image = self.instance.image
|
||||
self.sys_homeassistant.save_data()
|
||||
|
||||
@process_lock
|
||||
async def install_landingpage(self) -> None:
|
||||
@ -271,9 +91,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
self.sys_capture_exception(err)
|
||||
else:
|
||||
self.version = self.instance.version
|
||||
self.image = self.sys_updater.image_homeassistant
|
||||
self.save_data()
|
||||
self.sys_homeassistant.version = self.instance.version
|
||||
self.sys_homeassistant.image = self.sys_updater.image_homeassistant
|
||||
self.sys_homeassistant.save_data()
|
||||
break
|
||||
|
||||
# Start landingpage
|
||||
@ -287,10 +107,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
_LOGGER.info("Setup Home Assistant")
|
||||
while True:
|
||||
# read homeassistant tag and install it
|
||||
if not self.latest_version:
|
||||
if not self.sys_homeassistant.latest_version:
|
||||
await self.sys_updater.reload()
|
||||
|
||||
tag = self.latest_version
|
||||
tag = self.sys_homeassistant.latest_version
|
||||
if tag:
|
||||
try:
|
||||
await self.instance.update(
|
||||
@ -306,9 +126,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
await asyncio.sleep(30)
|
||||
|
||||
_LOGGER.info("Home Assistant docker now installed")
|
||||
self.version = self.instance.version
|
||||
self.image = self.sys_updater.image_homeassistant
|
||||
self.save_data()
|
||||
self.sys_homeassistant.version = self.instance.version
|
||||
self.sys_homeassistant.image = self.sys_updater.image_homeassistant
|
||||
self.sys_homeassistant.save_data()
|
||||
|
||||
# finishing
|
||||
try:
|
||||
@ -324,9 +144,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
@process_lock
|
||||
async def update(self, version: Optional[str] = None) -> None:
|
||||
"""Update HomeAssistant version."""
|
||||
version = version or self.latest_version
|
||||
old_image = self.image
|
||||
rollback = self.version if not self.error_state else None
|
||||
version = version or self.sys_homeassistant.latest_version
|
||||
old_image = self.sys_homeassistant.image
|
||||
rollback = self.sys_homeassistant.version if not self.error_state else None
|
||||
running = await self.instance.is_running()
|
||||
exists = await self.instance.exists()
|
||||
|
||||
@ -346,15 +166,15 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
_LOGGER.warning("Update Home Assistant image fails")
|
||||
raise HomeAssistantUpdateError()
|
||||
else:
|
||||
self.version = self.instance.version
|
||||
self.image = self.sys_updater.image_homeassistant
|
||||
self.sys_homeassistant.version = self.instance.version
|
||||
self.sys_homeassistant.image = self.sys_updater.image_homeassistant
|
||||
|
||||
if running:
|
||||
await self._start()
|
||||
_LOGGER.info("Successful run Home Assistant %s", to_version)
|
||||
|
||||
# Successfull - last step
|
||||
self.save_data()
|
||||
self.sys_homeassistant.save_data()
|
||||
with suppress(DockerAPIError):
|
||||
await self.instance.cleanup(old_image=old_image)
|
||||
|
||||
@ -384,18 +204,18 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
async def _start(self) -> None:
|
||||
"""Start Home Assistant Docker & wait."""
|
||||
# Create new API token
|
||||
self._data[ATTR_ACCESS_TOKEN] = secrets.token_hex(56)
|
||||
self.save_data()
|
||||
self.sys_homeassistant.supervisor_token = secrets.token_hex(56)
|
||||
self.sys_homeassistant.save_data()
|
||||
|
||||
# Write audio settings
|
||||
self.write_pulse()
|
||||
self.sys_homeassistant.write_pulse()
|
||||
|
||||
try:
|
||||
await self.instance.run()
|
||||
except DockerAPIError:
|
||||
raise HomeAssistantError()
|
||||
|
||||
await self._block_till_run(self.version)
|
||||
await self._block_till_run(self.sys_homeassistant.version)
|
||||
|
||||
@process_lock
|
||||
async def start(self) -> None:
|
||||
@ -411,7 +231,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
except DockerAPIError:
|
||||
raise HomeAssistantError()
|
||||
|
||||
await self._block_till_run(self.version)
|
||||
await self._block_till_run(self.sys_homeassistant.version)
|
||||
# No Instance/Container found, extended start
|
||||
else:
|
||||
await self._start()
|
||||
@ -435,7 +255,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
except DockerAPIError:
|
||||
raise HomeAssistantError()
|
||||
|
||||
await self._block_till_run(self.version)
|
||||
await self._block_till_run(self.sys_homeassistant.version)
|
||||
|
||||
@process_lock
|
||||
async def rebuild(self) -> None:
|
||||
@ -503,101 +323,6 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
_LOGGER.info("Home Assistant config is valid")
|
||||
return ConfigResult(True, log)
|
||||
|
||||
async def ensure_access_token(self) -> None:
|
||||
"""Ensure there is an access token."""
|
||||
if (
|
||||
self.access_token is not None
|
||||
and self._access_token_expires > datetime.utcnow()
|
||||
):
|
||||
return
|
||||
|
||||
with suppress(asyncio.TimeoutError, aiohttp.ClientError):
|
||||
async with self.sys_websession_ssl.post(
|
||||
f"{self.api_url}/auth/token",
|
||||
timeout=30,
|
||||
data={
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": self.refresh_token,
|
||||
},
|
||||
) as resp:
|
||||
if resp.status != 200:
|
||||
_LOGGER.error("Can't update Home Assistant access token!")
|
||||
raise HomeAssistantAuthError()
|
||||
|
||||
_LOGGER.info("Updated Home Assistant API token")
|
||||
tokens = await resp.json()
|
||||
self.access_token = tokens["access_token"]
|
||||
self._access_token_expires = datetime.utcnow() + timedelta(
|
||||
seconds=tokens["expires_in"]
|
||||
)
|
||||
|
||||
@asynccontextmanager
|
||||
async def make_request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
json: Optional[Dict[str, Any]] = None,
|
||||
content_type: Optional[str] = None,
|
||||
data: Any = None,
|
||||
timeout: int = 30,
|
||||
params: Optional[Dict[str, str]] = None,
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
) -> AsyncContextManager[aiohttp.ClientResponse]:
|
||||
"""Async context manager to make a request with right auth."""
|
||||
url = f"{self.api_url}/{path}"
|
||||
headers = headers or {}
|
||||
|
||||
# Passthrough content type
|
||||
if content_type is not None:
|
||||
headers[hdrs.CONTENT_TYPE] = content_type
|
||||
|
||||
for _ in (1, 2):
|
||||
# Prepare Access token
|
||||
if self.refresh_token:
|
||||
await self.ensure_access_token()
|
||||
headers[hdrs.AUTHORIZATION] = f"Bearer {self.access_token}"
|
||||
|
||||
try:
|
||||
async with getattr(self.sys_websession_ssl, method)(
|
||||
url,
|
||||
data=data,
|
||||
timeout=timeout,
|
||||
json=json,
|
||||
headers=headers,
|
||||
params=params,
|
||||
) as resp:
|
||||
# Access token expired
|
||||
if resp.status == 401 and self.refresh_token:
|
||||
self.access_token = None
|
||||
continue
|
||||
yield resp
|
||||
return
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
|
||||
_LOGGER.error("Error on call %s: %s", url, err)
|
||||
break
|
||||
|
||||
raise HomeAssistantAPIError()
|
||||
|
||||
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/config") as resp:
|
||||
if resp.status in (200, 201):
|
||||
data = await resp.json()
|
||||
if data.get("state", "RUNNING") == "RUNNING":
|
||||
return True
|
||||
else:
|
||||
_LOGGER.debug("Home Assistant API return: %d", resp.status)
|
||||
|
||||
return False
|
||||
|
||||
async def _block_till_run(self, version: str) -> None:
|
||||
"""Block until Home-Assistant is booting up or startup timeout."""
|
||||
# Skip landingpage
|
||||
@ -631,7 +356,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
break
|
||||
|
||||
# 2: Check if API response
|
||||
if await self.check_api_state():
|
||||
if await self.sys_homeassistant.api.check_api_state():
|
||||
_LOGGER.info("Detect a running Home Assistant instance")
|
||||
self._error_state = False
|
||||
return
|
||||
@ -659,7 +384,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
_LOGGER.info("Home Assistant pip installation done")
|
||||
|
||||
# 5: Timeout
|
||||
if timeout and time.monotonic() - start_time > self.wait_boot:
|
||||
if (
|
||||
timeout
|
||||
and time.monotonic() - start_time > self.sys_homeassistant.wait_boot
|
||||
):
|
||||
_LOGGER.warning("Don't wait anymore on Home Assistant startup!")
|
||||
break
|
||||
|
||||
@ -671,32 +399,13 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
if await self.instance.exists():
|
||||
return
|
||||
|
||||
_LOGGER.info("Repair Home Assistant %s", self.version)
|
||||
_LOGGER.info("Repair Home Assistant %s", self.sys_homeassistant.version)
|
||||
await self.sys_run_in_executor(
|
||||
self.sys_docker.network.stale_cleanup, self.instance.name
|
||||
)
|
||||
|
||||
# Pull image
|
||||
try:
|
||||
await self.instance.install(self.version)
|
||||
await self.instance.install(self.sys_homeassistant.version)
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Repairing of Home Assistant fails")
|
||||
|
||||
def write_pulse(self):
|
||||
"""Write asound config to file and return True on success."""
|
||||
pulse_config = self.sys_plugins.audio.pulse_client(
|
||||
input_profile=self.audio_input, output_profile=self.audio_output
|
||||
)
|
||||
|
||||
# Cleanup wrong maps
|
||||
if self.path_pulse.is_dir():
|
||||
shutil.rmtree(self.path_pulse, ignore_errors=True)
|
||||
|
||||
# Write pulse config
|
||||
try:
|
||||
with self.path_pulse.open("w") as config_file:
|
||||
config_file.write(pulse_config)
|
||||
except OSError as err:
|
||||
_LOGGER.error("Home Assistant can't write pulse/client.config: %s", err)
|
||||
else:
|
||||
_LOGGER.info("Update pulse/client.config: %s", self.path_pulse)
|
@ -12,7 +12,7 @@ from ..utils import AsyncThrottle
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SecretsManager(CoreSysAttributes):
|
||||
class HomeAssistantSecrets(CoreSysAttributes):
|
||||
"""Manage Home Assistant secrets."""
|
||||
|
||||
def __init__(self, coresys: CoreSys):
|
@ -156,13 +156,13 @@ class Ingress(JsonConfig, CoreSysAttributes):
|
||||
|
||||
async def update_hass_panel(self, addon: Addon):
|
||||
"""Return True if Home Assistant up and running."""
|
||||
if not await self.sys_homeassistant.is_running():
|
||||
if not await self.sys_homeassistant.core.is_running():
|
||||
_LOGGER.debug("Ignore panel update on Core")
|
||||
return
|
||||
|
||||
# Update UI
|
||||
method = "post" if addon.ingress_panel else "delete"
|
||||
async with self.sys_homeassistant.make_request(
|
||||
async with self.sys_homeassistant.api.make_request(
|
||||
method, f"api/hassio_push/panel/{addon.slug}"
|
||||
) as resp:
|
||||
if resp.status in (200, 201):
|
||||
|
@ -169,7 +169,7 @@ class Tasks(CoreSysAttributes):
|
||||
"""Check running state of Docker and start if they is close."""
|
||||
# if Home Assistant is active
|
||||
if (
|
||||
not await self.sys_homeassistant.is_fails()
|
||||
not await self.sys_homeassistant.core.is_fails()
|
||||
or not self.sys_homeassistant.watchdog
|
||||
or self.sys_homeassistant.error_state
|
||||
):
|
||||
@ -177,14 +177,14 @@ class Tasks(CoreSysAttributes):
|
||||
|
||||
# if Home Assistant is running
|
||||
if (
|
||||
self.sys_homeassistant.in_progress
|
||||
or await self.sys_homeassistant.is_running()
|
||||
self.sys_homeassistant.core.in_progress
|
||||
or await self.sys_homeassistant.core.is_running()
|
||||
):
|
||||
return
|
||||
|
||||
_LOGGER.warning("Watchdog found a problem with Home Assistant Docker!")
|
||||
try:
|
||||
await self.sys_homeassistant.start()
|
||||
await self.sys_homeassistant.core.start()
|
||||
except HomeAssistantError:
|
||||
_LOGGER.error("Watchdog Home Assistant reanimation fails!")
|
||||
|
||||
@ -196,7 +196,7 @@ class Tasks(CoreSysAttributes):
|
||||
"""
|
||||
# If Home-Assistant is active
|
||||
if (
|
||||
not await self.sys_homeassistant.is_fails()
|
||||
not await self.sys_homeassistant.core.is_fails()
|
||||
or not self.sys_homeassistant.watchdog
|
||||
or self.sys_homeassistant.error_state
|
||||
):
|
||||
@ -207,8 +207,8 @@ class Tasks(CoreSysAttributes):
|
||||
|
||||
# If Home-Assistant API is up
|
||||
if (
|
||||
self.sys_homeassistant.in_progress
|
||||
or await self.sys_homeassistant.check_api_state()
|
||||
self.sys_homeassistant.core.in_progress
|
||||
or await self.sys_homeassistant.api.check_api_state()
|
||||
):
|
||||
return
|
||||
|
||||
@ -221,7 +221,7 @@ class Tasks(CoreSysAttributes):
|
||||
|
||||
_LOGGER.error("Watchdog found a problem with Home Assistant API!")
|
||||
try:
|
||||
await self.sys_homeassistant.restart()
|
||||
await self.sys_homeassistant.core.restart()
|
||||
except HomeAssistantError:
|
||||
_LOGGER.error("Watchdog Home Assistant reanimation fails!")
|
||||
finally:
|
||||
|
@ -231,7 +231,7 @@ class SnapshotManager(CoreSysAttributes):
|
||||
_LOGGER.info("Restore %s run Home-Assistant", snapshot.slug)
|
||||
snapshot.restore_homeassistant()
|
||||
task_hass = self.sys_create_task(
|
||||
self.sys_homeassistant.update(snapshot.homeassistant_version)
|
||||
self.sys_homeassistant.core.update(snapshot.homeassistant_version)
|
||||
)
|
||||
|
||||
# Restore repositories
|
||||
@ -295,7 +295,7 @@ class SnapshotManager(CoreSysAttributes):
|
||||
async with snapshot:
|
||||
# Stop Home-Assistant for config restore
|
||||
if FOLDER_HOMEASSISTANT in folders:
|
||||
await self.sys_homeassistant.stop()
|
||||
await self.sys_homeassistant.core.stop()
|
||||
snapshot.restore_homeassistant()
|
||||
|
||||
# Process folders
|
||||
@ -308,7 +308,9 @@ class SnapshotManager(CoreSysAttributes):
|
||||
if homeassistant:
|
||||
_LOGGER.info("Restore %s run Home-Assistant", snapshot.slug)
|
||||
task_hass = self.sys_create_task(
|
||||
self.sys_homeassistant.update(snapshot.homeassistant_version)
|
||||
self.sys_homeassistant.core.update(
|
||||
snapshot.homeassistant_version
|
||||
)
|
||||
)
|
||||
|
||||
if addons:
|
||||
@ -324,13 +326,13 @@ class SnapshotManager(CoreSysAttributes):
|
||||
await task_hass
|
||||
|
||||
# Do we need start HomeAssistant?
|
||||
if not await self.sys_homeassistant.is_running():
|
||||
if not await self.sys_homeassistant.core.is_running():
|
||||
await self.sys_homeassistant.start()
|
||||
|
||||
# Check If we can access to API / otherwise restart
|
||||
if not await self.sys_homeassistant.check_api_state():
|
||||
if not await self.sys_homeassistant.api.check_api_state():
|
||||
_LOGGER.warning("Need restart HomeAssistant for API")
|
||||
await self.sys_homeassistant.restart()
|
||||
await self.sys_homeassistant.core.restart()
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Restore %s error", snapshot.slug)
|
||||
|
Loading…
x
Reference in New Issue
Block a user