supervisor/hassio/supervisor.py
Pascal Vizeli 2fc5e3b7d9
Repair / fixup docker overlayfs issues (#1170)
* Add a repair modus

* Add repair to add-ons

* repair to cli

* Add API call

* fix sync call

* Clean all images

* Fix repair

* Fix supervisor

* Add new function to core

* fix tagging

* better style

* use retag

* new retag function

* Fix lint

* Fix import export
2019-08-07 17:26:32 +02:00

150 lines
4.7 KiB
Python

"""Home Assistant control object."""
import asyncio
from contextlib import suppress
from ipaddress import IPv4Address
import logging
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Awaitable, Optional
import aiohttp
from .const import URL_HASSIO_APPARMOR, HASSIO_VERSION
from .coresys import CoreSys, CoreSysAttributes
from .docker.stats import DockerStats
from .docker.supervisor import DockerSupervisor
from .exceptions import (
DockerAPIError,
HostAppArmorError,
SupervisorError,
SupervisorUpdateError,
)
_LOGGER = logging.getLogger(__name__)
class Supervisor(CoreSysAttributes):
"""Home Assistant core object for handle it."""
def __init__(self, coresys: CoreSys):
"""Initialize hass object."""
self.coresys: CoreSys = coresys
self.instance: DockerSupervisor = DockerSupervisor(coresys)
async def load(self) -> None:
"""Prepare Home Assistant object."""
try:
await self.instance.attach(tag="latest")
except DockerAPIError:
_LOGGER.fatal("Can't setup Supervisor Docker container!")
with suppress(DockerAPIError):
await self.instance.cleanup()
@property
def ip_address(self) -> IPv4Address:
"""Return IP of Supervisor instance."""
return self.instance.ip_address
@property
def need_update(self) -> bool:
"""Return True if an update is available."""
return self.version != self.latest_version
@property
def version(self) -> str:
"""Return version of running Home Assistant."""
return HASSIO_VERSION
@property
def latest_version(self) -> str:
"""Return last available version of Home Assistant."""
return self.sys_updater.version_hassio
@property
def image(self) -> str:
"""Return image name of Home Assistant container."""
return self.instance.image
@property
def arch(self) -> str:
"""Return arch of the Hass.io container."""
return self.instance.arch
async def update_apparmor(self) -> None:
"""Fetch last version and update profile."""
url = URL_HASSIO_APPARMOR
try:
_LOGGER.info("Fetch AppArmor profile %s", url)
async with self.sys_websession.get(url, timeout=10) as request:
data = await request.text()
except (aiohttp.ClientError, asyncio.TimeoutError) as err:
_LOGGER.warning("Can't fetch AppArmor profile: %s", err)
raise SupervisorError() from None
with TemporaryDirectory(dir=self.sys_config.path_tmp) as tmp_dir:
profile_file = Path(tmp_dir, "apparmor.txt")
try:
profile_file.write_text(data)
except OSError as err:
_LOGGER.error("Can't write temporary profile: %s", err)
raise SupervisorError() from None
try:
await self.sys_host.apparmor.load_profile(
"hassio-supervisor", profile_file
)
except HostAppArmorError:
_LOGGER.error("Can't update AppArmor profile!")
raise SupervisorError() from None
async def update(self, version: Optional[str] = None) -> None:
"""Update Home Assistant version."""
version = version or self.latest_version
if version == self.sys_supervisor.version:
_LOGGER.warning("Version %s is already installed", version)
return
_LOGGER.info("Update Supervisor to version %s", version)
try:
await self.instance.install(version, latest=True)
except DockerAPIError:
_LOGGER.error("Update of Hass.io fails!")
raise SupervisorUpdateError() from None
with suppress(SupervisorError):
await self.update_apparmor()
self.sys_loop.call_later(1, self.sys_loop.stop)
@property
def in_progress(self) -> bool:
"""Return True if a task is in progress."""
return self.instance.in_progress
def logs(self) -> Awaitable[bytes]:
"""Get Supervisor docker logs.
Return Coroutine.
"""
return self.instance.logs()
async def stats(self) -> DockerStats:
"""Return stats of Supervisor."""
try:
return await self.instance.stats()
except DockerAPIError:
raise SupervisorError() from None
async def repair(self):
"""Repair local Supervisor data."""
if await self.instance.exists():
return
_LOGGER.info("Repair Supervisor %s", self.version)
try:
await self.instance.retag()
except DockerAPIError:
_LOGGER.error("Repairing of Supervisor fails")