Make homeassistant container constant (#808)

* Make homeassistant container constant

* Update homeassistant.py

* Update homeassistant.py

* Update interface.py

* Update homeassistant.py

* Fix handling

* add start function

* Add typing

* Fix lint

* Add API call

* Update logs

* Fix some issue with watchdog

* Fix lint
This commit is contained in:
Pascal Vizeli 2019-03-27 17:20:05 +01:00 committed by GitHub
parent 4eb02f474d
commit b52f90187b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 351 additions and 210 deletions

1
API.md
View File

@ -376,6 +376,7 @@ Output is the raw Docker log.
- POST `/homeassistant/check` - POST `/homeassistant/check`
- POST `/homeassistant/start` - POST `/homeassistant/start`
- POST `/homeassistant/stop` - POST `/homeassistant/stop`
- POST `/homeassistant/rebuild`
- POST `/homeassistant/options` - POST `/homeassistant/options`

View File

@ -1,23 +1,24 @@
"""Init file for Hass.io RESTful API.""" """Init file for Hass.io RESTful API."""
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Optional
from aiohttp import web from aiohttp import web
from ..coresys import CoreSys, CoreSysAttributes
from .addons import APIAddons from .addons import APIAddons
from .auth import APIAuth from .auth import APIAuth
from .discovery import APIDiscovery from .discovery import APIDiscovery
from .homeassistant import APIHomeAssistant
from .hardware import APIHardware from .hardware import APIHardware
from .host import APIHost
from .hassos import APIHassOS from .hassos import APIHassOS
from .homeassistant import APIHomeAssistant
from .host import APIHost
from .info import APIInfo from .info import APIInfo
from .proxy import APIProxy from .proxy import APIProxy
from .supervisor import APISupervisor
from .snapshots import APISnapshots
from .services import APIServices
from .security import SecurityMiddleware from .security import SecurityMiddleware
from ..coresys import CoreSysAttributes from .services import APIServices
from .snapshots import APISnapshots
from .supervisor import APISupervisor
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -25,18 +26,18 @@ _LOGGER = logging.getLogger(__name__)
class RestAPI(CoreSysAttributes): class RestAPI(CoreSysAttributes):
"""Handle RESTful API for Hass.io.""" """Handle RESTful API for Hass.io."""
def __init__(self, coresys): def __init__(self, coresys: CoreSys):
"""Initialize Docker base wrapper.""" """Initialize Docker base wrapper."""
self.coresys = coresys self.coresys: CoreSys = coresys
self.security = SecurityMiddleware(coresys) self.security: SecurityMiddleware = SecurityMiddleware(coresys)
self.webapp = web.Application( self.webapp: web.Application = web.Application(
middlewares=[self.security.token_validation]) middlewares=[self.security.token_validation])
# service stuff # service stuff
self._runner = web.AppRunner(self.webapp) self._runner: web.AppRunner = web.AppRunner(self.webapp)
self._site = None self._site: Optional[web.TCPSite] = None
async def load(self): async def load(self) -> None:
"""Register REST API Calls.""" """Register REST API Calls."""
self._register_supervisor() self._register_supervisor()
self._register_host() self._register_host()
@ -52,7 +53,7 @@ class RestAPI(CoreSysAttributes):
self._register_info() self._register_info()
self._register_auth() self._register_auth()
def _register_host(self): def _register_host(self) -> None:
"""Register hostcontrol functions.""" """Register hostcontrol functions."""
api_host = APIHost() api_host = APIHost()
api_host.coresys = self.coresys api_host.coresys = self.coresys
@ -72,7 +73,7 @@ class RestAPI(CoreSysAttributes):
api_host.service_reload), api_host.service_reload),
]) ])
def _register_hassos(self): def _register_hassos(self) -> None:
"""Register HassOS functions.""" """Register HassOS functions."""
api_hassos = APIHassOS() api_hassos = APIHassOS()
api_hassos.coresys = self.coresys api_hassos.coresys = self.coresys
@ -84,7 +85,7 @@ class RestAPI(CoreSysAttributes):
web.post('/hassos/config/sync', api_hassos.config_sync), web.post('/hassos/config/sync', api_hassos.config_sync),
]) ])
def _register_hardware(self): def _register_hardware(self) -> None:
"""Register hardware functions.""" """Register hardware functions."""
api_hardware = APIHardware() api_hardware = APIHardware()
api_hardware.coresys = self.coresys api_hardware.coresys = self.coresys
@ -94,7 +95,7 @@ class RestAPI(CoreSysAttributes):
web.get('/hardware/audio', api_hardware.audio), web.get('/hardware/audio', api_hardware.audio),
]) ])
def _register_info(self): def _register_info(self) -> None:
"""Register info functions.""" """Register info functions."""
api_info = APIInfo() api_info = APIInfo()
api_info.coresys = self.coresys api_info.coresys = self.coresys
@ -103,7 +104,7 @@ class RestAPI(CoreSysAttributes):
web.get('/info', api_info.info), web.get('/info', api_info.info),
]) ])
def _register_auth(self): def _register_auth(self) -> None:
"""Register auth functions.""" """Register auth functions."""
api_auth = APIAuth() api_auth = APIAuth()
api_auth.coresys = self.coresys api_auth.coresys = self.coresys
@ -112,7 +113,7 @@ class RestAPI(CoreSysAttributes):
web.post('/auth', api_auth.auth), web.post('/auth', api_auth.auth),
]) ])
def _register_supervisor(self): def _register_supervisor(self) -> None:
"""Register Supervisor functions.""" """Register Supervisor functions."""
api_supervisor = APISupervisor() api_supervisor = APISupervisor()
api_supervisor.coresys = self.coresys api_supervisor.coresys = self.coresys
@ -127,7 +128,7 @@ class RestAPI(CoreSysAttributes):
web.post('/supervisor/options', api_supervisor.options), web.post('/supervisor/options', api_supervisor.options),
]) ])
def _register_homeassistant(self): def _register_homeassistant(self) -> None:
"""Register Home Assistant functions.""" """Register Home Assistant functions."""
api_hass = APIHomeAssistant() api_hass = APIHomeAssistant()
api_hass.coresys = self.coresys api_hass.coresys = self.coresys
@ -142,9 +143,10 @@ class RestAPI(CoreSysAttributes):
web.post('/homeassistant/stop', api_hass.stop), web.post('/homeassistant/stop', api_hass.stop),
web.post('/homeassistant/start', api_hass.start), web.post('/homeassistant/start', api_hass.start),
web.post('/homeassistant/check', api_hass.check), web.post('/homeassistant/check', api_hass.check),
web.post('/homeassistant/rebuild', api_hass.rebuild),
]) ])
def _register_proxy(self): def _register_proxy(self) -> None:
"""Register Home Assistant API Proxy.""" """Register Home Assistant API Proxy."""
api_proxy = APIProxy() api_proxy = APIProxy()
api_proxy.coresys = self.coresys api_proxy.coresys = self.coresys
@ -158,7 +160,7 @@ class RestAPI(CoreSysAttributes):
web.get('/homeassistant/api/', api_proxy.api), web.get('/homeassistant/api/', api_proxy.api),
]) ])
def _register_addons(self): def _register_addons(self) -> None:
"""Register Add-on functions.""" """Register Add-on functions."""
api_addons = APIAddons() api_addons = APIAddons()
api_addons.coresys = self.coresys api_addons.coresys = self.coresys
@ -184,7 +186,7 @@ class RestAPI(CoreSysAttributes):
web.get('/addons/{addon}/stats', api_addons.stats), web.get('/addons/{addon}/stats', api_addons.stats),
]) ])
def _register_snapshots(self): def _register_snapshots(self) -> None:
"""Register snapshots functions.""" """Register snapshots functions."""
api_snapshots = APISnapshots() api_snapshots = APISnapshots()
api_snapshots.coresys = self.coresys api_snapshots.coresys = self.coresys
@ -204,7 +206,7 @@ class RestAPI(CoreSysAttributes):
web.get('/snapshots/{snapshot}/download', api_snapshots.download), web.get('/snapshots/{snapshot}/download', api_snapshots.download),
]) ])
def _register_services(self): def _register_services(self) -> None:
"""Register services functions.""" """Register services functions."""
api_services = APIServices() api_services = APIServices()
api_services.coresys = self.coresys api_services.coresys = self.coresys
@ -216,7 +218,7 @@ class RestAPI(CoreSysAttributes):
web.delete('/services/{service}', api_services.del_service), web.delete('/services/{service}', api_services.del_service),
]) ])
def _register_discovery(self): def _register_discovery(self) -> None:
"""Register discovery functions.""" """Register discovery functions."""
api_discovery = APIDiscovery() api_discovery = APIDiscovery()
api_discovery.coresys = self.coresys api_discovery.coresys = self.coresys
@ -228,7 +230,7 @@ class RestAPI(CoreSysAttributes):
web.post('/discovery', api_discovery.set_discovery), web.post('/discovery', api_discovery.set_discovery),
]) ])
def _register_panel(self): def _register_panel(self) -> None:
"""Register panel for Home Assistant.""" """Register panel for Home Assistant."""
panel_dir = Path(__file__).parent.joinpath("panel") panel_dir = Path(__file__).parent.joinpath("panel")
@ -256,7 +258,7 @@ class RestAPI(CoreSysAttributes):
# This route is for HA > 0.70 # This route is for HA > 0.70
self.webapp.add_routes([web.static('/app', panel_dir)]) self.webapp.add_routes([web.static('/app', panel_dir)])
async def start(self): async def start(self) -> None:
"""Run RESTful API webserver.""" """Run RESTful API webserver."""
await self._runner.setup() await self._runner.setup()
self._site = web.TCPSite( self._site = web.TCPSite(
@ -270,7 +272,7 @@ class RestAPI(CoreSysAttributes):
else: else:
_LOGGER.info("Start API on %s", self.sys_docker.network.supervisor) _LOGGER.info("Start API on %s", self.sys_docker.network.supervisor)
async def stop(self): async def stop(self) -> None:
"""Stop RESTful API webserver.""" """Stop RESTful API webserver."""
if not self._site: if not self._site:
return return

View File

@ -1,15 +1,34 @@
"""Init file for Hass.io Home Assistant RESTful API.""" """Init file for Hass.io Home Assistant RESTful API."""
import asyncio import asyncio
import logging import logging
from typing import Coroutine, Dict, Any
import voluptuous as vol import voluptuous as vol
from aiohttp import web
from ..const import ( from ..const import (
ATTR_ARCH, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_BOOT, ATTR_CPU_PERCENT, ATTR_ARCH,
ATTR_CUSTOM, ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_MACHINE, ATTR_MEMORY_LIMIT, ATTR_BLK_READ,
ATTR_MEMORY_USAGE, ATTR_NETWORK_RX, ATTR_NETWORK_TX, ATTR_PASSWORD, ATTR_BLK_WRITE,
ATTR_PORT, ATTR_REFRESH_TOKEN, ATTR_SSL, ATTR_VERSION, ATTR_WAIT_BOOT, ATTR_BOOT,
ATTR_WATCHDOG, CONTENT_TYPE_BINARY) ATTR_CPU_PERCENT,
ATTR_CUSTOM,
ATTR_IMAGE,
ATTR_LAST_VERSION,
ATTR_MACHINE,
ATTR_MEMORY_LIMIT,
ATTR_MEMORY_USAGE,
ATTR_NETWORK_RX,
ATTR_NETWORK_TX,
ATTR_PASSWORD,
ATTR_PORT,
ATTR_REFRESH_TOKEN,
ATTR_SSL,
ATTR_VERSION,
ATTR_WAIT_BOOT,
ATTR_WATCHDOG,
CONTENT_TYPE_BINARY,
)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import APIError from ..exceptions import APIError
from ..validate import DOCKER_IMAGE, NETWORK_PORT from ..validate import DOCKER_IMAGE, NETWORK_PORT
@ -18,37 +37,28 @@ from .utils import api_process, api_process_raw, api_validate
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
SCHEMA_OPTIONS = vol.Schema({ SCHEMA_OPTIONS = vol.Schema(
vol.Optional(ATTR_BOOT): {
vol.Boolean(), vol.Optional(ATTR_BOOT): vol.Boolean(),
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): vol.Inclusive(ATTR_IMAGE, "custom_hass"): vol.Maybe(vol.Coerce(str)),
vol.Maybe(vol.Coerce(str)), vol.Inclusive(ATTR_LAST_VERSION, "custom_hass"): vol.Any(None, DOCKER_IMAGE),
vol.Inclusive(ATTR_LAST_VERSION, 'custom_hass'): vol.Optional(ATTR_PORT): NETWORK_PORT,
vol.Any(None, DOCKER_IMAGE), vol.Optional(ATTR_PASSWORD): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_PORT): vol.Optional(ATTR_SSL): vol.Boolean(),
NETWORK_PORT, vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
vol.Optional(ATTR_PASSWORD): vol.Optional(ATTR_WAIT_BOOT): vol.All(vol.Coerce(int), vol.Range(min=60)),
vol.Maybe(vol.Coerce(str)), vol.Optional(ATTR_REFRESH_TOKEN): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_SSL): }
vol.Boolean(), )
vol.Optional(ATTR_WATCHDOG):
vol.Boolean(),
vol.Optional(ATTR_WAIT_BOOT):
vol.All(vol.Coerce(int), vol.Range(min=60)),
vol.Optional(ATTR_REFRESH_TOKEN):
vol.Maybe(vol.Coerce(str)),
})
SCHEMA_VERSION = vol.Schema({ SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
vol.Optional(ATTR_VERSION): vol.Coerce(str),
})
class APIHomeAssistant(CoreSysAttributes): class APIHomeAssistant(CoreSysAttributes):
"""Handle RESTful API for Home Assistant functions.""" """Handle RESTful API for Home Assistant functions."""
@api_process @api_process
async def info(self, request): async def info(self, request: web.Request) -> Dict[str, Any]:
"""Return host information.""" """Return host information."""
return { return {
ATTR_VERSION: self.sys_homeassistant.version, ATTR_VERSION: self.sys_homeassistant.version,
@ -65,7 +75,7 @@ class APIHomeAssistant(CoreSysAttributes):
} }
@api_process @api_process
async def options(self, request): async def options(self, request: web.Request) -> None:
"""Set Home Assistant options.""" """Set Home Assistant options."""
body = await api_validate(SCHEMA_OPTIONS, request) body = await api_validate(SCHEMA_OPTIONS, request)
@ -81,6 +91,7 @@ class APIHomeAssistant(CoreSysAttributes):
if ATTR_PASSWORD in body: if ATTR_PASSWORD in body:
self.sys_homeassistant.api_password = body[ATTR_PASSWORD] self.sys_homeassistant.api_password = body[ATTR_PASSWORD]
self.sys_homeassistant.refresh_token = None
if ATTR_SSL in body: if ATTR_SSL in body:
self.sys_homeassistant.api_ssl = body[ATTR_SSL] self.sys_homeassistant.api_ssl = body[ATTR_SSL]
@ -97,7 +108,7 @@ class APIHomeAssistant(CoreSysAttributes):
self.sys_homeassistant.save_data() self.sys_homeassistant.save_data()
@api_process @api_process
async def stats(self, request): async def stats(self, request: web.Request) -> Dict[Any, str]:
"""Return resource information.""" """Return resource information."""
stats = await self.sys_homeassistant.stats() stats = await self.sys_homeassistant.stats()
if not stats: if not stats:
@ -114,7 +125,7 @@ class APIHomeAssistant(CoreSysAttributes):
} }
@api_process @api_process
async def update(self, request): async def update(self, request: web.Request) -> None:
"""Update Home Assistant.""" """Update Home Assistant."""
body = await api_validate(SCHEMA_VERSION, request) body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.sys_homeassistant.last_version) version = body.get(ATTR_VERSION, self.sys_homeassistant.last_version)
@ -122,27 +133,32 @@ class APIHomeAssistant(CoreSysAttributes):
await asyncio.shield(self.sys_homeassistant.update(version)) await asyncio.shield(self.sys_homeassistant.update(version))
@api_process @api_process
def stop(self, request): def stop(self, request: web.Request) -> Coroutine:
"""Stop Home Assistant.""" """Stop Home Assistant."""
return asyncio.shield(self.sys_homeassistant.stop()) return asyncio.shield(self.sys_homeassistant.stop())
@api_process @api_process
def start(self, request): def start(self, request: web.Request) -> Coroutine:
"""Start Home Assistant.""" """Start Home Assistant."""
return asyncio.shield(self.sys_homeassistant.start()) return asyncio.shield(self.sys_homeassistant.start())
@api_process @api_process
def restart(self, request): def restart(self, request: web.Request) -> Coroutine:
"""Restart Home Assistant.""" """Restart Home Assistant."""
return asyncio.shield(self.sys_homeassistant.restart()) return asyncio.shield(self.sys_homeassistant.restart())
@api_process
def rebuild(self, request: web.Request) -> Coroutine:
"""Rebuild Home Assistant."""
return asyncio.shield(self.sys_homeassistant.rebuild())
@api_process_raw(CONTENT_TYPE_BINARY) @api_process_raw(CONTENT_TYPE_BINARY)
def logs(self, request): def logs(self, request: web.Request) -> Coroutine:
"""Return Home Assistant Docker logs.""" """Return Home Assistant Docker logs."""
return self.sys_homeassistant.logs() return self.sys_homeassistant.logs()
@api_process @api_process
async def check(self, request): async def check(self, request: web.Request) -> None:
"""Check configuration of Home Assistant.""" """Check configuration of Home Assistant."""
result = await self.sys_homeassistant.check_config() result = await self.sys_homeassistant.check_config()
if not result.valid: if not result.valid:

View File

@ -6,8 +6,12 @@ import logging
import async_timeout import async_timeout
from .coresys import CoreSysAttributes from .coresys import CoreSysAttributes
from .const import (STARTUP_SYSTEM, STARTUP_SERVICES, STARTUP_APPLICATION, from .const import (
STARTUP_INITIALIZE) STARTUP_SYSTEM,
STARTUP_SERVICES,
STARTUP_APPLICATION,
STARTUP_INITIALIZE,
)
from .exceptions import HassioError, HomeAssistantError from .exceptions import HassioError, HomeAssistantError
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -108,7 +112,7 @@ class HassIO(CoreSysAttributes):
await self.sys_tasks.load() await self.sys_tasks.load()
# If landingpage / run upgrade in background # If landingpage / run upgrade in background
if self.sys_homeassistant.version == 'landingpage': if self.sys_homeassistant.version == "landingpage":
self.sys_create_task(self.sys_homeassistant.install()) self.sys_create_task(self.sys_homeassistant.install())
_LOGGER.info("Hass.io is up and running") _LOGGER.info("Hass.io is up and running")
@ -121,12 +125,14 @@ class HassIO(CoreSysAttributes):
# process async stop tasks # process async stop tasks
try: try:
with async_timeout.timeout(10): with async_timeout.timeout(10):
await asyncio.wait([ await asyncio.wait(
self.sys_api.stop(), [
self.sys_dns.stop(), self.sys_api.stop(),
self.sys_websession.close(), self.sys_dns.stop(),
self.sys_websession_ssl.close() self.sys_websession.close(),
]) self.sys_websession_ssl.close(),
]
)
except asyncio.TimeoutError: except asyncio.TimeoutError:
_LOGGER.warning("Force Shutdown!") _LOGGER.warning("Force Shutdown!")

View File

@ -17,7 +17,7 @@ class DockerHassOSCli(DockerInterface, CoreSysAttributes):
"""Return name of HassOS CLI image.""" """Return name of HassOS CLI image."""
return f"homeassistant/{self.sys_arch.supervisor}-hassio-cli" return f"homeassistant/{self.sys_arch.supervisor}-hassio-cli"
def _stop(self): def _stop(self, remove_container=True):
"""Don't need stop.""" """Don't need stop."""
return True return True
@ -33,5 +33,6 @@ class DockerHassOSCli(DockerInterface, CoreSysAttributes):
else: else:
self._meta = image.attrs self._meta = image.attrs
_LOGGER.info("Found HassOS CLI %s with version %s", self.image, _LOGGER.info(
self.version) "Found HassOS CLI %s with version %s", self.image, self.version
)

View File

@ -8,7 +8,7 @@ from ..const import ENV_TOKEN, ENV_TIME, LABEL_MACHINE
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
HASS_DOCKER_NAME = 'homeassistant' HASS_DOCKER_NAME = "homeassistant"
class DockerHomeAssistant(DockerInterface): class DockerHomeAssistant(DockerInterface):
@ -17,8 +17,8 @@ class DockerHomeAssistant(DockerInterface):
@property @property
def machine(self): def machine(self):
"""Return machine of Home Assistant Docker image.""" """Return machine of Home Assistant Docker image."""
if self._meta and LABEL_MACHINE in self._meta['Config']['Labels']: if self._meta and LABEL_MACHINE in self._meta["Config"]["Labels"]:
return self._meta['Config']['Labels'][LABEL_MACHINE] return self._meta["Config"]["Labels"][LABEL_MACHINE]
return None return None
@property @property
@ -58,25 +58,29 @@ class DockerHomeAssistant(DockerInterface):
privileged=True, privileged=True,
init=True, init=True,
devices=self.devices, devices=self.devices,
network_mode='host', network_mode="host",
environment={ environment={
'HASSIO': self.sys_docker.network.supervisor, "HASSIO": self.sys_docker.network.supervisor,
ENV_TIME: self.sys_timezone, ENV_TIME: self.sys_timezone,
ENV_TOKEN: self.sys_homeassistant.hassio_token, ENV_TOKEN: self.sys_homeassistant.hassio_token,
}, },
volumes={ volumes={
str(self.sys_config.path_extern_homeassistant): str(self.sys_config.path_extern_homeassistant): {
{'bind': '/config', 'mode': 'rw'}, "bind": "/config",
str(self.sys_config.path_extern_ssl): "mode": "rw",
{'bind': '/ssl', 'mode': 'ro'}, },
str(self.sys_config.path_extern_share): str(self.sys_config.path_extern_ssl): {"bind": "/ssl", "mode": "ro"},
{'bind': '/share', 'mode': 'rw'}, str(self.sys_config.path_extern_share): {
} "bind": "/share",
"mode": "rw",
},
},
) )
if ret: if ret:
_LOGGER.info("Start homeassistant %s with version %s", _LOGGER.info(
self.image, self.version) "Start homeassistant %s with version %s", self.image, self.version
)
return ret return ret
@ -94,17 +98,18 @@ class DockerHomeAssistant(DockerInterface):
detach=True, detach=True,
stdout=True, stdout=True,
stderr=True, stderr=True,
environment={ environment={ENV_TIME: self.sys_timezone},
ENV_TIME: self.sys_timezone,
},
volumes={ volumes={
str(self.sys_config.path_extern_homeassistant): str(self.sys_config.path_extern_homeassistant): {
{'bind': '/config', 'mode': 'rw'}, "bind": "/config",
str(self.sys_config.path_extern_ssl): "mode": "rw",
{'bind': '/ssl', 'mode': 'ro'}, },
str(self.sys_config.path_extern_share): str(self.sys_config.path_extern_ssl): {"bind": "/ssl", "mode": "ro"},
{'bind': '/share', 'mode': 'ro'}, str(self.sys_config.path_extern_share): {
} "bind": "/share",
"mode": "ro",
},
},
) )
def is_initialize(self): def is_initialize(self):
@ -117,8 +122,13 @@ class DockerHomeAssistant(DockerInterface):
Need run inside executor. Need run inside executor.
""" """
try: try:
self.sys_docker.containers.get(self.name) docker_container = self.sys_docker.containers.get(self.name)
docker_image = self.sys_docker.images.get(self.image)
except docker.errors.DockerException: except docker.errors.DockerException:
return False return False
# we run on an old image, stop and start it
if docker_container.image.id != docker_image.id:
return False
return True return True

View File

@ -5,10 +5,10 @@ import logging
import docker import docker
from .stats import DockerStats from ..const import LABEL_ARCH, LABEL_VERSION
from ..const import LABEL_VERSION, LABEL_ARCH
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..utils import process_lock from ..utils import process_lock
from .stats import DockerStats
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -37,17 +37,17 @@ class DockerInterface(CoreSysAttributes):
"""Return meta data of configuration for container/image.""" """Return meta data of configuration for container/image."""
if not self._meta: if not self._meta:
return {} return {}
return self._meta.get('Config', {}) return self._meta.get("Config", {})
@property @property
def meta_labels(self): def meta_labels(self):
"""Return meta data of labels for container/image.""" """Return meta data of labels for container/image."""
return self.meta_config.get('Labels') or {} return self.meta_config.get("Labels") or {}
@property @property
def image(self): def image(self):
"""Return name of Docker image.""" """Return name of Docker image."""
return self.meta_config.get('Image') return self.meta_config.get("Image")
@property @property
def version(self): def version(self):
@ -80,7 +80,7 @@ class DockerInterface(CoreSysAttributes):
_LOGGER.info("Pull image %s tag %s.", image, tag) _LOGGER.info("Pull image %s tag %s.", image, tag)
docker_image = self.sys_docker.images.pull(f"{image}:{tag}") docker_image = self.sys_docker.images.pull(f"{image}:{tag}")
docker_image.tag(image, tag='latest') docker_image.tag(image, tag="latest")
self._meta = docker_image.attrs self._meta = docker_image.attrs
except docker.errors.APIError as err: except docker.errors.APIError as err:
_LOGGER.error("Can't install %s:%s -> %s.", image, tag, err) _LOGGER.error("Can't install %s:%s -> %s.", image, tag, err)
@ -125,7 +125,7 @@ class DockerInterface(CoreSysAttributes):
return False return False
# container is not running # container is not running
if docker_container.status != 'running': if docker_container.status != "running":
return False return False
# we run on an old image, stop and start it # we run on an old image, stop and start it
@ -152,8 +152,7 @@ class DockerInterface(CoreSysAttributes):
except docker.errors.DockerException: except docker.errors.DockerException:
return False return False
_LOGGER.info("Attach to image %s with version %s", self.image, _LOGGER.info("Attach to image %s with version %s", self.image, self.version)
self.version)
return True return True
@ -170,12 +169,12 @@ class DockerInterface(CoreSysAttributes):
raise NotImplementedError() raise NotImplementedError()
@process_lock @process_lock
def stop(self): def stop(self, remove_container=True):
"""Stop/remove Docker container.""" """Stop/remove Docker container."""
return self.sys_run_in_executor(self._stop) return self.sys_run_in_executor(self._stop, remove_container)
def _stop(self): def _stop(self, remove_container=True):
"""Stop/remove and remove docker container. """Stop/remove Docker container.
Need run inside executor. Need run inside executor.
""" """
@ -184,14 +183,39 @@ class DockerInterface(CoreSysAttributes):
except docker.errors.DockerException: except docker.errors.DockerException:
return False return False
if docker_container.status == 'running': if docker_container.status == "running":
_LOGGER.info("Stop %s Docker application", self.image) _LOGGER.info("Stop %s Docker application", self.image)
with suppress(docker.errors.DockerException): with suppress(docker.errors.DockerException):
docker_container.stop(timeout=self.timeout) docker_container.stop(timeout=self.timeout)
with suppress(docker.errors.DockerException): if remove_container:
_LOGGER.info("Clean %s Docker application", self.image) with suppress(docker.errors.DockerException):
docker_container.remove(force=True) _LOGGER.info("Clean %s Docker application", self.image)
docker_container.remove(force=True)
return True
@process_lock
def start(self):
"""Start Docker container."""
return self.sys_run_in_executor(self._start)
def _start(self):
"""Start docker container.
Need run inside executor.
"""
try:
docker_container = self.sys_docker.containers.get(self.name)
except docker.errors.DockerException:
return False
_LOGGER.info("Start %s", self.image)
try:
docker_container.start()
except docker.errors.DockerException as err:
_LOGGER.error("Can't start %s: %s", self.image, err)
return False
return True return True
@ -208,17 +232,16 @@ class DockerInterface(CoreSysAttributes):
# Cleanup container # Cleanup container
self._stop() self._stop()
_LOGGER.info("Remove Docker %s with latest and %s", self.image, _LOGGER.info("Remove Docker %s with latest and %s", self.image, self.version)
self.version)
try: try:
with suppress(docker.errors.ImageNotFound): with suppress(docker.errors.ImageNotFound):
self.sys_docker.images.remove( self.sys_docker.images.remove(image=f"{self.image}:latest", force=True)
image=f"{self.image}:latest", force=True)
with suppress(docker.errors.ImageNotFound): with suppress(docker.errors.ImageNotFound):
self.sys_docker.images.remove( self.sys_docker.images.remove(
image=f"{self.image}:{self.version}", force=True) image=f"{self.image}:{self.version}", force=True
)
except docker.errors.DockerException as err: except docker.errors.DockerException as err:
_LOGGER.warning("Can't remove image %s: %s", self.image, err) _LOGGER.warning("Can't remove image %s: %s", self.image, err)
@ -239,8 +262,9 @@ class DockerInterface(CoreSysAttributes):
""" """
image = image or self.image image = image or self.image
_LOGGER.info("Update Docker %s:%s to %s:%s", self.image, self.version, _LOGGER.info(
image, tag) "Update Docker %s:%s to %s:%s", self.image, self.version, image, tag
)
# Update docker image # Update docker image
if not self._install(tag, image): if not self._install(tag, image):
@ -300,6 +324,29 @@ class DockerInterface(CoreSysAttributes):
return True return True
@process_lock
def restart(self):
"""Restart docker container."""
return self.sys_loop.run_in_executor(None, self._restart)
def _restart(self):
"""Restart docker container.
Need run inside executor.
"""
try:
container = self.sys_docker.containers.get(self.name)
except docker.errors.DockerException:
return False
_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)
return False
return True
@process_lock @process_lock
def execute_command(self, command): def execute_command(self, command):
"""Create a temporary container and run command.""" """Create a temporary container and run command."""
@ -332,3 +379,30 @@ class DockerInterface(CoreSysAttributes):
except docker.errors.DockerException as err: except docker.errors.DockerException as err:
_LOGGER.error("Can't read stats from %s: %s", self.name, err) _LOGGER.error("Can't read stats from %s: %s", self.name, err)
return None return None
def is_fails(self):
"""Return True if Docker is failing state.
Return a Future.
"""
return self.sys_run_in_executor(self._is_fails)
def _is_fails(self):
"""Return True if Docker is failing state.
Need run inside executor.
"""
try:
docker_container = self.sys_docker.containers.get(self.name)
except docker.errors.DockerException:
return False
# container is not running
if docker_container.status != "exited":
return False
# Check return value
if int(docker_container.attrs["State"]["ExitCode"]) != 0:
return True
return False

View File

@ -2,26 +2,44 @@
import asyncio import asyncio
from contextlib import asynccontextmanager, suppress from contextlib import asynccontextmanager, suppress
from datetime import datetime, timedelta from datetime import datetime, timedelta
from ipaddress import IPv4Address
import logging import logging
import os import os
import re
from pathlib import Path from pathlib import Path
import re
import socket import socket
import time import time
from typing import Any, AsyncContextManager, Coroutine, Dict, Optional
from uuid import UUID
import aiohttp import aiohttp
from aiohttp import hdrs from aiohttp import hdrs
import attr import attr
from .const import (FILE_HASSIO_HOMEASSISTANT, ATTR_IMAGE, ATTR_LAST_VERSION, from .const import (
ATTR_UUID, ATTR_BOOT, ATTR_PASSWORD, ATTR_PORT, ATTR_SSL, ATTR_ACCESS_TOKEN,
ATTR_WATCHDOG, ATTR_WAIT_BOOT, ATTR_REFRESH_TOKEN, ATTR_BOOT,
ATTR_ACCESS_TOKEN, HEADER_HA_ACCESS) ATTR_IMAGE,
from .coresys import CoreSysAttributes ATTR_LAST_VERSION,
ATTR_PASSWORD,
ATTR_PORT,
ATTR_REFRESH_TOKEN,
ATTR_SSL,
ATTR_UUID,
ATTR_WAIT_BOOT,
ATTR_WATCHDOG,
FILE_HASSIO_HOMEASSISTANT,
HEADER_HA_ACCESS,
)
from .coresys import CoreSys, CoreSysAttributes
from .docker.homeassistant import DockerHomeAssistant from .docker.homeassistant import DockerHomeAssistant
from .exceptions import (HomeAssistantUpdateError, HomeAssistantError, from .exceptions import (
HomeAssistantAPIError, HomeAssistantAuthError) HomeAssistantAPIError,
from .utils import convert_to_ascii, process_lock, create_token HomeAssistantAuthError,
HomeAssistantError,
HomeAssistantUpdateError,
)
from .utils import convert_to_ascii, create_token, process_lock
from .utils.json import JsonConfig from .utils.json import JsonConfig
from .validate import SCHEMA_HASS_CONFIG from .validate import SCHEMA_HASS_CONFIG
@ -40,18 +58,19 @@ class ConfigResult:
class HomeAssistant(JsonConfig, CoreSysAttributes): class HomeAssistant(JsonConfig, CoreSysAttributes):
"""Home Assistant core object for handle it.""" """Home Assistant core object for handle it."""
def __init__(self, coresys): def __init__(self, coresys: CoreSys):
"""Initialize Home Assistant object.""" """Initialize Home Assistant object."""
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG) super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
self.coresys = coresys self.coresys: CoreSys = coresys
self.instance = DockerHomeAssistant(coresys) self.instance: DockerHomeAssistant = DockerHomeAssistant(coresys)
self.lock = asyncio.Lock(loop=coresys.loop) self.lock: asyncio.Lock = asyncio.Lock(loop=coresys.loop)
self._error_state = False self._error_state: bool = False
# We don't persist access tokens. Instead we fetch new ones when needed
self.access_token = None
self._access_token_expires = None
async def load(self): # 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.""" """Prepare Home Assistant object."""
if await self.instance.attach(): if await self.instance.attach():
return return
@ -60,95 +79,95 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
await self.install_landingpage() await self.install_landingpage()
@property @property
def machine(self): def machine(self) -> str:
"""Return the system machines.""" """Return the system machines."""
return self.instance.machine return self.instance.machine
@property @property
def arch(self): def arch(self) -> str:
"""Return arch of running Home Assistant.""" """Return arch of running Home Assistant."""
return self.instance.arch return self.instance.arch
@property @property
def error_state(self): def error_state(self) -> bool:
"""Return True if system is in error.""" """Return True if system is in error."""
return self._error_state return self._error_state
@property @property
def api_ip(self): def api_ip(self) -> IPv4Address:
"""Return IP of Home Assistant instance.""" """Return IP of Home Assistant instance."""
return self.sys_docker.network.gateway return self.sys_docker.network.gateway
@property @property
def api_port(self): def api_port(self) -> int:
"""Return network port to Home Assistant instance.""" """Return network port to Home Assistant instance."""
return self._data[ATTR_PORT] return self._data[ATTR_PORT]
@api_port.setter @api_port.setter
def api_port(self, value): def api_port(self, value: int) -> None:
"""Set network port for Home Assistant instance.""" """Set network port for Home Assistant instance."""
self._data[ATTR_PORT] = value self._data[ATTR_PORT] = value
@property @property
def api_password(self): def api_password(self) -> str:
"""Return password for Home Assistant instance.""" """Return password for Home Assistant instance."""
return self._data.get(ATTR_PASSWORD) return self._data.get(ATTR_PASSWORD)
@api_password.setter @api_password.setter
def api_password(self, value): def api_password(self, value: str):
"""Set password for Home Assistant instance.""" """Set password for Home Assistant instance."""
self._data[ATTR_PASSWORD] = value self._data[ATTR_PASSWORD] = value
@property @property
def api_ssl(self): def api_ssl(self) -> bool:
"""Return if we need ssl to Home Assistant instance.""" """Return if we need ssl to Home Assistant instance."""
return self._data[ATTR_SSL] return self._data[ATTR_SSL]
@api_ssl.setter @api_ssl.setter
def api_ssl(self, value): def api_ssl(self, value: bool):
"""Set SSL for Home Assistant instance.""" """Set SSL for Home Assistant instance."""
self._data[ATTR_SSL] = value self._data[ATTR_SSL] = value
@property @property
def api_url(self): def api_url(self) -> str:
"""Return API url to Home Assistant.""" """Return API url to Home Assistant."""
return "{}://{}:{}".format('https' if self.api_ssl else 'http', return "{}://{}:{}".format('https' if self.api_ssl else 'http',
self.api_ip, self.api_port) self.api_ip, self.api_port)
@property @property
def watchdog(self): def watchdog(self) -> bool:
"""Return True if the watchdog should protect Home Assistant.""" """Return True if the watchdog should protect Home Assistant."""
return self._data[ATTR_WATCHDOG] return self._data[ATTR_WATCHDOG]
@watchdog.setter @watchdog.setter
def watchdog(self, value): def watchdog(self, value: bool):
"""Return True if the watchdog should protect Home Assistant.""" """Return True if the watchdog should protect Home Assistant."""
self._data[ATTR_WATCHDOG] = value self._data[ATTR_WATCHDOG] = value
@property @property
def wait_boot(self): def wait_boot(self) -> int:
"""Return time to wait for Home Assistant startup.""" """Return time to wait for Home Assistant startup."""
return self._data[ATTR_WAIT_BOOT] return self._data[ATTR_WAIT_BOOT]
@wait_boot.setter @wait_boot.setter
def wait_boot(self, value): def wait_boot(self, value: int):
"""Set time to wait for Home Assistant startup.""" """Set time to wait for Home Assistant startup."""
self._data[ATTR_WAIT_BOOT] = value self._data[ATTR_WAIT_BOOT] = value
@property @property
def version(self): def version(self) -> str:
"""Return version of running Home Assistant.""" """Return version of running Home Assistant."""
return self.instance.version return self.instance.version
@property @property
def last_version(self): def last_version(self) -> str:
"""Return last available version of Home Assistant.""" """Return last available version of Home Assistant."""
if self.is_custom_image: if self.is_custom_image:
return self._data.get(ATTR_LAST_VERSION) return self._data.get(ATTR_LAST_VERSION)
return self.sys_updater.version_homeassistant return self.sys_updater.version_homeassistant
@last_version.setter @last_version.setter
def last_version(self, value): def last_version(self, value: str):
"""Set last available version of Home Assistant.""" """Set last available version of Home Assistant."""
if value: if value:
self._data[ATTR_LAST_VERSION] = value self._data[ATTR_LAST_VERSION] = value
@ -156,14 +175,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
self._data.pop(ATTR_LAST_VERSION, None) self._data.pop(ATTR_LAST_VERSION, None)
@property @property
def image(self): def image(self) -> str:
"""Return image name of the Home Assistant container.""" """Return image name of the Home Assistant container."""
if self._data.get(ATTR_IMAGE): if self._data.get(ATTR_IMAGE):
return self._data[ATTR_IMAGE] return self._data[ATTR_IMAGE]
return os.environ['HOMEASSISTANT_REPOSITORY'] return os.environ['HOMEASSISTANT_REPOSITORY']
@image.setter @image.setter
def image(self, value): def image(self, value: str):
"""Set image name of Home Assistant container.""" """Set image name of Home Assistant container."""
if value: if value:
self._data[ATTR_IMAGE] = value self._data[ATTR_IMAGE] = value
@ -171,43 +190,43 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
self._data.pop(ATTR_IMAGE, None) self._data.pop(ATTR_IMAGE, None)
@property @property
def is_custom_image(self): def is_custom_image(self) -> bool:
"""Return True if a custom image is used.""" """Return True if a custom image is used."""
return all( return all(
attr in self._data for attr in (ATTR_IMAGE, ATTR_LAST_VERSION)) attr in self._data for attr in (ATTR_IMAGE, ATTR_LAST_VERSION))
@property @property
def boot(self): def boot(self) -> bool:
"""Return True if Home Assistant boot is enabled.""" """Return True if Home Assistant boot is enabled."""
return self._data[ATTR_BOOT] return self._data[ATTR_BOOT]
@boot.setter @boot.setter
def boot(self, value): def boot(self, value: bool):
"""Set Home Assistant boot options.""" """Set Home Assistant boot options."""
self._data[ATTR_BOOT] = value self._data[ATTR_BOOT] = value
@property @property
def uuid(self): def uuid(self) -> UUID:
"""Return a UUID of this Home Assistant instance.""" """Return a UUID of this Home Assistant instance."""
return self._data[ATTR_UUID] return self._data[ATTR_UUID]
@property @property
def hassio_token(self): def hassio_token(self) -> str:
"""Return an access token for the Hass.io API.""" """Return an access token for the Hass.io API."""
return self._data.get(ATTR_ACCESS_TOKEN) return self._data.get(ATTR_ACCESS_TOKEN)
@property @property
def refresh_token(self): def refresh_token(self) -> str:
"""Return the refresh token to authenticate with Home Assistant.""" """Return the refresh token to authenticate with Home Assistant."""
return self._data.get(ATTR_REFRESH_TOKEN) return self._data.get(ATTR_REFRESH_TOKEN)
@refresh_token.setter @refresh_token.setter
def refresh_token(self, value): def refresh_token(self, value: str):
"""Set Home Assistant refresh_token.""" """Set Home Assistant refresh_token."""
self._data[ATTR_REFRESH_TOKEN] = value self._data[ATTR_REFRESH_TOKEN] = value
@process_lock @process_lock
async def install_landingpage(self): async def install_landingpage(self) -> None:
"""Install a landing page.""" """Install a landing page."""
_LOGGER.info("Setup HomeAssistant landingpage") _LOGGER.info("Setup HomeAssistant landingpage")
while True: while True:
@ -217,7 +236,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
await asyncio.sleep(30) await asyncio.sleep(30)
@process_lock @process_lock
async def install(self): async def install(self) -> None:
"""Install a landing page.""" """Install a landing page."""
_LOGGER.info("Setup Home Assistant") _LOGGER.info("Setup Home Assistant")
while True: while True:
@ -244,7 +263,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
await self.instance.cleanup() await self.instance.cleanup()
@process_lock @process_lock
async def update(self, version=None): async def update(self, version=None) -> None:
"""Update HomeAssistant version.""" """Update HomeAssistant version."""
version = version or self.last_version version = version or self.last_version
rollback = self.version if not self.error_state else None rollback = self.version if not self.error_state else None
@ -258,14 +277,13 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
# process an update # process an update
async def _update(to_version): async def _update(to_version):
"""Run Home Assistant update.""" """Run Home Assistant update."""
try: _LOGGER.info("Update Home Assistant to version %s", to_version)
_LOGGER.info("Update Home Assistant to version %s", to_version) if not await self.instance.update(to_version):
if not await self.instance.update(to_version): raise HomeAssistantUpdateError()
raise HomeAssistantUpdateError()
finally: if running:
if running: await self._start()
await self._start() _LOGGER.info("Successful run Home Assistant %s", to_version)
_LOGGER.info("Successful run Home Assistant %s", to_version)
# Update Home Assistant # Update Home Assistant
with suppress(HomeAssistantError): with suppress(HomeAssistantError):
@ -279,7 +297,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
else: else:
raise HomeAssistantUpdateError() raise HomeAssistantUpdateError()
async def _start(self): async def _start(self) -> None:
"""Start Home Assistant Docker & wait.""" """Start Home Assistant Docker & wait."""
if await self.instance.is_running(): if await self.instance.is_running():
_LOGGER.warning("Home Assistant is already running!") _LOGGER.warning("Home Assistant is already running!")
@ -294,61 +312,74 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
await self._block_till_run() await self._block_till_run()
@process_lock @process_lock
def start(self): async def start(self) -> None:
"""Run Home Assistant docker. """Run Home Assistant docker."""
if await self.instance.is_running():
await self.instance.restart()
elif await self.instance.is_initialize():
await self.instance.start()
else:
await self._start()
return
Return a coroutine. await self._block_till_run()
"""
return self._start()
@process_lock @process_lock
def stop(self): def stop(self) -> Coroutine:
"""Stop Home Assistant Docker. """Stop Home Assistant Docker.
Return a coroutine. Return a coroutine.
""" """
return self.instance.stop() return self.instance.stop(remove_container=False)
@process_lock @process_lock
async def restart(self): async def restart(self) -> None:
"""Restart Home Assistant Docker.""" """Restart Home Assistant Docker."""
if not await self.instance.restart():
raise HomeAssistantError()
await self._block_till_run()
@process_lock
async def rebuild(self) -> None:
"""Rebuild Home Assistant Docker container."""
await self.instance.stop() await self.instance.stop()
await self._start() await self._start()
def logs(self): def logs(self) -> Coroutine:
"""Get HomeAssistant docker logs. """Get HomeAssistant docker logs.
Return a coroutine. Return a coroutine.
""" """
return self.instance.logs() return self.instance.logs()
def stats(self): def stats(self) -> Coroutine:
"""Return stats of Home Assistant. """Return stats of Home Assistant.
Return a coroutine. Return a coroutine.
""" """
return self.instance.stats() return self.instance.stats()
def is_running(self): def is_running(self) -> Coroutine:
"""Return True if Docker container is running. """Return True if Docker container is running.
Return a coroutine. Return a coroutine.
""" """
return self.instance.is_running() return self.instance.is_running()
def is_initialize(self): def is_fails(self) -> Coroutine:
"""Return True if a Docker container is exists. """Return True if a Docker container is fails state.
Return a coroutine. Return a coroutine.
""" """
return self.instance.is_initialize() return self.instance.is_fails()
@property @property
def in_progress(self): def in_progress(self) -> bool:
"""Return True if a task is in progress.""" """Return True if a task is in progress."""
return self.instance.in_progress or self.lock.locked() return self.instance.in_progress or self.lock.locked()
async def check_config(self): async def check_config(self) -> ConfigResult:
"""Run Home Assistant config check.""" """Run Home Assistant config check."""
result = await self.instance.execute_command( result = await self.instance.execute_command(
"python3 -m homeassistant -c /config --script check_config") "python3 -m homeassistant -c /config --script check_config")
@ -367,7 +398,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
_LOGGER.info("Home Assistant config is valid") _LOGGER.info("Home Assistant config is valid")
return ConfigResult(True, log) return ConfigResult(True, log)
async def ensure_access_token(self): async def ensure_access_token(self) -> None:
"""Ensures there is an access token.""" """Ensures there is an access token."""
if self.access_token is not None and self._access_token_expires > datetime.utcnow(): if self.access_token is not None and self._access_token_expires > datetime.utcnow():
return return
@ -392,12 +423,12 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
@asynccontextmanager @asynccontextmanager
async def make_request(self, async def make_request(self,
method, method: str,
path, path: str,
json=None, json: Optional[Dict[str, Any]] = None,
content_type=None, content_type: Optional[str] = None,
data=None, data: Optional[bytes] = None,
timeout=30): timeout=30) -> AsyncContextManager[aiohttp.ClientResponse]:
"""Async context manager to make a request with right auth.""" """Async context manager to make a request with right auth."""
url = f"{self.api_url}/{path}" url = f"{self.api_url}/{path}"
headers = {} headers = {}
@ -432,7 +463,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
raise HomeAssistantAPIError() raise HomeAssistantAPIError()
async def check_api_state(self): async def check_api_state(self) -> bool:
"""Return True if Home Assistant up and running.""" """Return True if Home Assistant up and running."""
with suppress(HomeAssistantAPIError): with suppress(HomeAssistantAPIError):
async with self.make_request('get', 'api/') as resp: async with self.make_request('get', 'api/') as resp:
@ -443,7 +474,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
return False return False
async def _block_till_run(self): async def _block_till_run(self) -> None:
"""Block until Home-Assistant is booting up or startup timeout.""" """Block until Home-Assistant is booting up or startup timeout."""
start_time = time.monotonic() start_time = time.monotonic()
migration_progress = False migration_progress = False

View File

@ -94,7 +94,7 @@ class Tasks(CoreSysAttributes):
async def _watchdog_homeassistant_docker(self): async def _watchdog_homeassistant_docker(self):
"""Check running state of Docker and start if they is close.""" """Check running state of Docker and start if they is close."""
# if Home Assistant is active # if Home Assistant is active
if not await self.sys_homeassistant.is_initialize() or \ if not await self.sys_homeassistant.is_fails() or \
not self.sys_homeassistant.watchdog or \ not self.sys_homeassistant.watchdog or \
self.sys_homeassistant.error_state: self.sys_homeassistant.error_state:
return return
@ -117,7 +117,7 @@ class Tasks(CoreSysAttributes):
a delay in our system. a delay in our system.
""" """
# If Home-Assistant is active # If Home-Assistant is active
if not await self.sys_homeassistant.is_initialize() or \ if not await self.sys_homeassistant.is_fails() or \
not self.sys_homeassistant.watchdog or \ not self.sys_homeassistant.watchdog or \
self.sys_homeassistant.error_state: self.sys_homeassistant.error_state:
return return