mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-19 15:16:33 +00:00
Collection of minor changes (#1793)
* Collection of minor changes * Process smaller comments * Fix HA version handling * More type hints * Review comment from Martin * Add protection against TypeError in version parsing
This commit is contained in:
parent
351c45da75
commit
44416edfd2
@ -130,12 +130,9 @@ class Addon(AddonModel):
|
|||||||
return {**self.data[ATTR_OPTIONS], **self.persist[ATTR_OPTIONS]}
|
return {**self.data[ATTR_OPTIONS], **self.persist[ATTR_OPTIONS]}
|
||||||
|
|
||||||
@options.setter
|
@options.setter
|
||||||
def options(self, value: Optional[Dict[str, Any]]):
|
def options(self, value: Optional[Dict[str, Any]]) -> None:
|
||||||
"""Store user add-on options."""
|
"""Store user add-on options."""
|
||||||
if value is None:
|
self.persist[ATTR_OPTIONS] = {} if value is None else deepcopy(value)
|
||||||
self.persist[ATTR_OPTIONS] = {}
|
|
||||||
else:
|
|
||||||
self.persist[ATTR_OPTIONS] = deepcopy(value)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def boot(self) -> bool:
|
def boot(self) -> bool:
|
||||||
|
@ -539,13 +539,16 @@ class AddonModel(CoreSysAttributes, ABC):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Home Assistant
|
# Home Assistant
|
||||||
version = config.get(ATTR_HOMEASSISTANT) or self.sys_homeassistant.version
|
version = config.get(ATTR_HOMEASSISTANT)
|
||||||
if pkg_version.parse(self.sys_homeassistant.version) < pkg_version.parse(
|
if version is None or self.sys_homeassistant.version is None:
|
||||||
version
|
return True
|
||||||
):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
try:
|
||||||
|
return pkg_version.parse(
|
||||||
|
self.sys_homeassistant.version
|
||||||
|
) >= pkg_version.parse(version)
|
||||||
|
except pkg_version.InvalidVersion:
|
||||||
|
return True
|
||||||
|
|
||||||
def _image(self, config) -> str:
|
def _image(self, config) -> str:
|
||||||
"""Generate image name from data."""
|
"""Generate image name from data."""
|
||||||
|
@ -148,39 +148,36 @@ class APIAddons(CoreSysAttributes):
|
|||||||
@api_process
|
@api_process
|
||||||
async def list(self, request: web.Request) -> Dict[str, Any]:
|
async def list(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return all add-ons or repositories."""
|
"""Return all add-ons or repositories."""
|
||||||
data_addons = []
|
data_addons = [
|
||||||
for addon in self.sys_addons.all:
|
{
|
||||||
data_addons.append(
|
ATTR_NAME: addon.name,
|
||||||
{
|
ATTR_SLUG: addon.slug,
|
||||||
ATTR_NAME: addon.name,
|
ATTR_DESCRIPTON: addon.description,
|
||||||
ATTR_SLUG: addon.slug,
|
ATTR_ADVANCED: addon.advanced,
|
||||||
ATTR_DESCRIPTON: addon.description,
|
ATTR_STAGE: addon.stage,
|
||||||
ATTR_ADVANCED: addon.advanced,
|
ATTR_VERSION: addon.latest_version,
|
||||||
ATTR_STAGE: addon.stage,
|
ATTR_INSTALLED: addon.version if addon.is_installed else None,
|
||||||
ATTR_VERSION: addon.latest_version,
|
ATTR_AVAILABLE: addon.available,
|
||||||
ATTR_INSTALLED: addon.version if addon.is_installed else None,
|
ATTR_DETACHED: addon.is_detached,
|
||||||
ATTR_AVAILABLE: addon.available,
|
ATTR_REPOSITORY: addon.repository,
|
||||||
ATTR_DETACHED: addon.is_detached,
|
ATTR_BUILD: addon.need_build,
|
||||||
ATTR_REPOSITORY: addon.repository,
|
ATTR_URL: addon.url,
|
||||||
ATTR_BUILD: addon.need_build,
|
ATTR_ICON: addon.with_icon,
|
||||||
ATTR_URL: addon.url,
|
ATTR_LOGO: addon.with_logo,
|
||||||
ATTR_ICON: addon.with_icon,
|
}
|
||||||
ATTR_LOGO: addon.with_logo,
|
for addon in self.sys_addons.all
|
||||||
}
|
]
|
||||||
)
|
|
||||||
|
|
||||||
data_repositories = []
|
|
||||||
for repository in self.sys_store.all:
|
|
||||||
data_repositories.append(
|
|
||||||
{
|
|
||||||
ATTR_SLUG: repository.slug,
|
|
||||||
ATTR_NAME: repository.name,
|
|
||||||
ATTR_SOURCE: repository.source,
|
|
||||||
ATTR_URL: repository.url,
|
|
||||||
ATTR_MAINTAINER: repository.maintainer,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
data_repositories = [
|
||||||
|
{
|
||||||
|
ATTR_SLUG: repository.slug,
|
||||||
|
ATTR_NAME: repository.name,
|
||||||
|
ATTR_SOURCE: repository.source,
|
||||||
|
ATTR_URL: repository.url,
|
||||||
|
ATTR_MAINTAINER: repository.maintainer,
|
||||||
|
}
|
||||||
|
for repository in self.sys_store.all
|
||||||
|
]
|
||||||
return {ATTR_ADDONS: data_addons, ATTR_REPOSITORIES: data_repositories}
|
return {ATTR_ADDONS: data_addons, ATTR_REPOSITORIES: data_repositories}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
@ -449,7 +446,4 @@ def _pretty_devices(addon: AnyAddon) -> List[str]:
|
|||||||
|
|
||||||
def _pretty_services(addon: AnyAddon) -> List[str]:
|
def _pretty_services(addon: AnyAddon) -> List[str]:
|
||||||
"""Return a simplified services role list."""
|
"""Return a simplified services role list."""
|
||||||
services = []
|
return [f"{name}:{access}" for name, access in addon.services_role.items()]
|
||||||
for name, access in addon.services_role.items():
|
|
||||||
services.append(f"{name}:{access}")
|
|
||||||
return services
|
|
||||||
|
@ -5,8 +5,8 @@ from ..const import (
|
|||||||
ATTR_ADDON,
|
ATTR_ADDON,
|
||||||
ATTR_CONFIG,
|
ATTR_CONFIG,
|
||||||
ATTR_DISCOVERY,
|
ATTR_DISCOVERY,
|
||||||
ATTR_SERVICES,
|
|
||||||
ATTR_SERVICE,
|
ATTR_SERVICE,
|
||||||
|
ATTR_SERVICES,
|
||||||
ATTR_UUID,
|
ATTR_UUID,
|
||||||
REQUEST_FROM,
|
REQUEST_FROM,
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Init file for Supervisor hardware RESTful API."""
|
"""Init file for Supervisor hardware RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any, Awaitable, Dict
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
@ -52,6 +52,6 @@ class APIHardware(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def trigger(self, request: web.Request) -> None:
|
def trigger(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Trigger a udev device reload."""
|
"""Trigger a udev device reload."""
|
||||||
return asyncio.shield(self.sys_hardware.udev_trigger())
|
return asyncio.shield(self.sys_hardware.udev_trigger())
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Init file for Supervisor Home Assistant RESTful API."""
|
"""Init file for Supervisor Home Assistant RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Coroutine, Dict
|
from typing import Any, Awaitable, Dict
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@ -141,27 +141,27 @@ 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: web.Request) -> Coroutine:
|
def stop(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""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: web.Request) -> Coroutine:
|
def start(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""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: web.Request) -> Coroutine:
|
def restart(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Restart Home Assistant."""
|
"""Restart Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.restart())
|
return asyncio.shield(self.sys_homeassistant.restart())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def rebuild(self, request: web.Request) -> Coroutine:
|
def rebuild(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Rebuild Home Assistant."""
|
"""Rebuild Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.rebuild())
|
return asyncio.shield(self.sys_homeassistant.rebuild())
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request: web.Request) -> Coroutine:
|
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
||||||
"""Return Home Assistant Docker logs."""
|
"""Return Home Assistant Docker logs."""
|
||||||
return self.sys_homeassistant.logs()
|
return self.sys_homeassistant.logs()
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
|
|||||||
MACHINE_ID = Path("/etc/machine-id")
|
MACHINE_ID = Path("/etc/machine-id")
|
||||||
|
|
||||||
|
|
||||||
async def initialize_coresys() -> None:
|
async def initialize_coresys() -> CoreSys:
|
||||||
"""Initialize supervisor coresys/objects."""
|
"""Initialize supervisor coresys/objects."""
|
||||||
coresys = CoreSys()
|
coresys = CoreSys()
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
"""Interface class for D-Bus wrappers."""
|
"""Interface class for D-Bus wrappers."""
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from ..utils.gdbus import DBus
|
from ..utils.gdbus import DBus
|
||||||
|
|
||||||
|
|
||||||
class DBusInterface:
|
class DBusInterface(ABC):
|
||||||
"""Handle D-Bus interface for hostname/system."""
|
"""Handle D-Bus interface for hostname/system."""
|
||||||
|
|
||||||
dbus: Optional[DBus] = None
|
dbus: Optional[DBus] = None
|
||||||
@ -14,6 +15,6 @@ class DBusInterface:
|
|||||||
"""Return True, if they is connected to D-Bus."""
|
"""Return True, if they is connected to D-Bus."""
|
||||||
return self.dbus is not None
|
return self.dbus is not None
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
"""Connect to D-Bus."""
|
"""Connect to D-Bus."""
|
||||||
raise NotImplementedError()
|
|
||||||
|
@ -107,9 +107,9 @@ class DockerAPI:
|
|||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
name: str = kwargs.get("name")
|
name: Optional[str] = kwargs.get("name")
|
||||||
network_mode: str = kwargs.get("network_mode")
|
network_mode: Optional[str] = kwargs.get("network_mode")
|
||||||
hostname: str = kwargs.get("hostname")
|
hostname: Optional[str] = kwargs.get("hostname")
|
||||||
|
|
||||||
# Setup DNS
|
# Setup DNS
|
||||||
if dns:
|
if dns:
|
||||||
|
@ -47,7 +47,7 @@ class DockerAddon(DockerInterface):
|
|||||||
self.addon = addon
|
self.addon = addon
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image(self) -> str:
|
def image(self) -> Optional[str]:
|
||||||
"""Return name of Docker image."""
|
"""Return name of Docker image."""
|
||||||
return self.addon.image
|
return self.addon.image
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ class DockerAddon(DockerInterface):
|
|||||||
return not self.addon.protected and self.addon.with_full_access
|
return not self.addon.protected and self.addon.with_full_access
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def environment(self) -> Dict[str, str]:
|
def environment(self) -> Dict[str, Optional[str]]:
|
||||||
"""Return environment for Docker add-on."""
|
"""Return environment for Docker add-on."""
|
||||||
addon_env = self.addon.environment or {}
|
addon_env = self.addon.environment or {}
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ class DockerAddon(DockerInterface):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def network_mapping(self) -> Dict[str, str]:
|
def network_mapping(self) -> Dict[str, IPv4Address]:
|
||||||
"""Return hosts mapping."""
|
"""Return hosts mapping."""
|
||||||
return {
|
return {
|
||||||
"supervisor": self.sys_docker.network.supervisor,
|
"supervisor": self.sys_docker.network.supervisor,
|
||||||
|
@ -134,11 +134,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
except docker.errors.DockerException:
|
except docker.errors.DockerException:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# container is not running
|
return docker_container.status == "running"
|
||||||
if docker_container.status != "running":
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
def attach(self, tag: str):
|
def attach(self, tag: str):
|
||||||
@ -301,6 +297,8 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.warning("Can't grep logs from %s: %s", self.image, err)
|
_LOGGER.warning("Can't grep logs from %s: %s", self.image, err)
|
||||||
|
|
||||||
|
return b""
|
||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
def cleanup(self, old_image: Optional[str] = None) -> Awaitable[None]:
|
def cleanup(self, old_image: Optional[str] = None) -> Awaitable[None]:
|
||||||
"""Check if old version exists and cleanup."""
|
"""Check if old version exists and cleanup."""
|
||||||
@ -412,10 +410,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Check return value
|
# Check return value
|
||||||
if int(docker_container.attrs["State"]["ExitCode"]) != 0:
|
return int(docker_container.attrs["State"]["ExitCode"]) != 0
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_latest_version(self, key: Any = int) -> Awaitable[str]:
|
def get_latest_version(self, key: Any = int) -> Awaitable[str]:
|
||||||
"""Return latest version of local Home Asssistant image."""
|
"""Return latest version of local Home Asssistant image."""
|
||||||
|
@ -202,12 +202,12 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
return self._data[ATTR_UUID]
|
return self._data[ATTR_UUID]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supervisor_token(self) -> str:
|
def supervisor_token(self) -> Optional[str]:
|
||||||
"""Return an access token for the Supervisor API."""
|
"""Return an access token for the Supervisor API."""
|
||||||
return self._data.get(ATTR_ACCESS_TOKEN)
|
return self._data.get(ATTR_ACCESS_TOKEN)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def refresh_token(self) -> str:
|
def refresh_token(self) -> Optional[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)
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class PluginManager(CoreSysAttributes):
|
|||||||
"""Return multicast handler."""
|
"""Return multicast handler."""
|
||||||
return self._multicast
|
return self._multicast
|
||||||
|
|
||||||
async def load(self):
|
async def load(self) -> None:
|
||||||
"""Load Supervisor plugins."""
|
"""Load Supervisor plugins."""
|
||||||
# Sequential to avoid issue on slow IO
|
# Sequential to avoid issue on slow IO
|
||||||
for plugin in (
|
for plugin in (
|
||||||
@ -94,7 +94,7 @@ class PluginManager(CoreSysAttributes):
|
|||||||
required_version,
|
required_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def repair(self):
|
async def repair(self) -> None:
|
||||||
"""Repair Supervisor plugins."""
|
"""Repair Supervisor plugins."""
|
||||||
await asyncio.wait(
|
await asyncio.wait(
|
||||||
[
|
[
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""Handle internal services discovery."""
|
"""Handle internal services discovery."""
|
||||||
from typing import Dict, List
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from .const import SERVICE_MQTT, SERVICE_MYSQL
|
from .const import SERVICE_MQTT, SERVICE_MYSQL
|
||||||
@ -25,7 +25,7 @@ class ServiceManager(CoreSysAttributes):
|
|||||||
"""Return a list of services."""
|
"""Return a list of services."""
|
||||||
return list(self.services_obj.values())
|
return list(self.services_obj.values())
|
||||||
|
|
||||||
def get(self, slug: str) -> ServiceInterface:
|
def get(self, slug: str) -> Optional[ServiceInterface]:
|
||||||
"""Return service object from slug."""
|
"""Return service object from slug."""
|
||||||
return self.services_obj.get(slug)
|
return self.services_obj.get(slug)
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ class SecureTarFile:
|
|||||||
|
|
||||||
# Encryption/Description
|
# Encryption/Description
|
||||||
self._aes: Optional[Cipher] = None
|
self._aes: Optional[Cipher] = None
|
||||||
self._key: bytes = key
|
self._key: Optional[bytes] = key
|
||||||
|
|
||||||
# Function helper
|
# Function helper
|
||||||
self._decrypt: Optional[CipherContext] = None
|
self._decrypt: Optional[CipherContext] = None
|
||||||
@ -101,7 +101,7 @@ class SecureTarFile:
|
|||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def size(self) -> int:
|
def size(self) -> float:
|
||||||
"""Return snapshot size."""
|
"""Return snapshot size."""
|
||||||
if not self._name.is_file():
|
if not self._name.is_file():
|
||||||
return 0
|
return 0
|
||||||
|
@ -36,8 +36,8 @@ from .const import (
|
|||||||
ATTR_VERSION,
|
ATTR_VERSION,
|
||||||
ATTR_WAIT_BOOT,
|
ATTR_WAIT_BOOT,
|
||||||
ATTR_WATCHDOG,
|
ATTR_WATCHDOG,
|
||||||
LogLevel,
|
|
||||||
SUPERVISOR_VERSION,
|
SUPERVISOR_VERSION,
|
||||||
|
LogLevel,
|
||||||
UpdateChannels,
|
UpdateChannels,
|
||||||
)
|
)
|
||||||
from .utils.validate import validate_timezone
|
from .utils.validate import validate_timezone
|
||||||
|
Loading…
x
Reference in New Issue
Block a user