mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-09 02:06:30 +00:00
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:
parent
4eb02f474d
commit
b52f90187b
1
API.md
1
API.md
@ -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`
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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!")
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user