Use updater for image data (#1628)

* Use updater for image data

* Fix message

* Fix handling

* Update code

* fix lint

* fix names

* Fix log

* Fix error log

* make it better
This commit is contained in:
Pascal Vizeli 2020-04-05 00:47:05 +02:00 committed by GitHub
parent 9350e4f961
commit fcebc9d1ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 226 additions and 85 deletions

View File

@ -92,7 +92,7 @@ class APISupervisor(CoreSysAttributes):
return { return {
ATTR_VERSION: SUPERVISOR_VERSION, ATTR_VERSION: SUPERVISOR_VERSION,
ATTR_VERSION_LATEST: self.sys_updater.version_hassio, ATTR_VERSION_LATEST: self.sys_updater.version_supervisor,
ATTR_CHANNEL: self.sys_updater.channel, ATTR_CHANNEL: self.sys_updater.channel,
ATTR_ARCH: self.sys_supervisor.arch, ATTR_ARCH: self.sys_supervisor.arch,
ATTR_IP_ADDRESS: str(self.sys_supervisor.ip_address), ATTR_IP_ADDRESS: str(self.sys_supervisor.ip_address),
@ -153,7 +153,7 @@ class APISupervisor(CoreSysAttributes):
async def update(self, request: web.Request) -> None: async def update(self, request: web.Request) -> None:
"""Update Supervisor OS.""" """Update Supervisor OS."""
body = await api_validate(SCHEMA_VERSION, request) body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.sys_updater.version_hassio) version = body.get(ATTR_VERSION, self.sys_updater.version_supervisor)
if version == self.sys_supervisor.version: if version == self.sys_supervisor.version:
raise APIError("Version {} is already in use".format(version)) raise APIError("Version {} is already in use".format(version))

View File

@ -128,36 +128,40 @@ class Audio(JsonConfig, CoreSysAttributes):
if self.latest_version: if self.latest_version:
with suppress(DockerAPIError): with suppress(DockerAPIError):
await self.instance.install(self.latest_version) await self.instance.install(
self.latest_version, image=self.sys_updater.image_audio
)
break break
_LOGGER.warning("Error on install Audio plugin. Retry in 30sec") _LOGGER.warning("Error on install Audio plugin. Retry in 30sec")
await asyncio.sleep(30) await asyncio.sleep(30)
_LOGGER.info("Audio plugin now installed") _LOGGER.info("Audio plugin now installed")
self.version = self.instance.version self.version = self.instance.version
self.image = self.instance.image self.image = self.sys_updater.image_audio
self.save_data() self.save_data()
async def update(self, version: Optional[str] = None) -> None: async def update(self, version: Optional[str] = None) -> None:
"""Update Audio plugin.""" """Update Audio plugin."""
version = version or self.latest_version version = version or self.latest_version
old_image = self.image
if version == self.version: if version == self.version:
_LOGGER.warning("Version %s is already installed for Audio", version) _LOGGER.warning("Version %s is already installed for Audio", version)
return return
try: try:
await self.instance.update(version) await self.instance.update(version, image=self.sys_updater.image_audio)
except DockerAPIError: except DockerAPIError:
_LOGGER.error("Audio update fails") _LOGGER.error("Audio update fails")
raise AudioUpdateError() from None raise AudioUpdateError() from None
else: else:
self.version = version
self.image = self.sys_updater.image_audio
self.save_data()
# Cleanup # Cleanup
with suppress(DockerAPIError): with suppress(DockerAPIError):
await self.instance.cleanup() await self.instance.cleanup(old_image=old_image)
self.version = version
self.save_data()
# Start Audio # Start Audio
await self.start() await self.start()

View File

@ -12,7 +12,14 @@ from .api import RestAPI
from .arch import CpuArch from .arch import CpuArch
from .auth import Auth from .auth import Auth
from .audio import Audio from .audio import Audio
from .const import SOCKET_DOCKER, UpdateChannels from .const import (
SOCKET_DOCKER,
UpdateChannels,
ENV_SUPERVISOR_SHARE,
ENV_SUPERVISOR_NAME,
ENV_HOMEASSISTANT_REPOSITORY,
ENV_SUPERVISOR_MACHINE,
)
from .core import Core from .core import Core
from .cli import HaCli from .cli import HaCli
from .coresys import CoreSys from .coresys import CoreSys
@ -35,9 +42,6 @@ from .utils.dt import fetch_timezone
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
ENV_SHARE = "SUPERVISOR_SHARE"
ENV_NAME = "SUPERVISOR_NAME"
ENV_REPO = "HOMEASSISTANT_REPOSITORY"
MACHINE_ID = Path("/etc/machine-id") MACHINE_ID = Path("/etc/machine-id")
@ -81,6 +85,13 @@ async def initialize_coresys():
if coresys.config.timezone == "UTC": if coresys.config.timezone == "UTC":
coresys.config.timezone = await fetch_timezone(coresys.websession) coresys.config.timezone = await fetch_timezone(coresys.websession)
# Set machine type
if os.environ.get(ENV_SUPERVISOR_MACHINE):
coresys.machine = os.environ[ENV_SUPERVISOR_MACHINE]
elif os.environ.get(ENV_HOMEASSISTANT_REPOSITORY):
coresys.machine = os.environ[ENV_HOMEASSISTANT_REPOSITORY][14:-14]
_LOGGER.info("Setup coresys for machine: %s", coresys.machine)
return coresys return coresys
@ -165,7 +176,7 @@ def migrate_system_env(coresys: CoreSys):
config = coresys.config config = coresys.config
# hass.io 0.37 -> 0.38 # hass.io 0.37 -> 0.38
old_build = Path(config.path_hassio, "addons/build") old_build = Path(config.path_supervisor, "addons/build")
if old_build.is_dir(): if old_build.is_dir():
try: try:
old_build.rmdir() old_build.rmdir()
@ -202,12 +213,20 @@ def initialize_logging():
def check_environment() -> None: def check_environment() -> None:
"""Check if all environment are exists.""" """Check if all environment are exists."""
# check environment variables # check environment variables
for key in (ENV_SHARE, ENV_NAME, ENV_REPO): for key in (ENV_SUPERVISOR_SHARE, ENV_SUPERVISOR_NAME):
try: try:
os.environ[key] os.environ[key]
except KeyError: except KeyError:
_LOGGER.fatal("Can't find %s in env!", key) _LOGGER.fatal("Can't find %s in env!", key)
# Check Machine info
if not os.environ.get(ENV_HOMEASSISTANT_REPOSITORY) and not os.environ.get(
ENV_SUPERVISOR_MACHINE
):
_LOGGER.fatal("Can't find any kind of machine/homeassistant details!")
elif not os.environ.get(ENV_SUPERVISOR_MACHINE):
_LOGGER.info("Use the old homeassistant repository for machine extraction")
# check docker socket # check docker socket
if not SOCKET_DOCKER.is_socket(): if not SOCKET_DOCKER.is_socket():
_LOGGER.fatal("Can't find Docker socket!") _LOGGER.fatal("Can't find Docker socket!")

View File

@ -102,36 +102,44 @@ class HaCli(CoreSysAttributes, JsonConfig):
if self.latest_version: if self.latest_version:
with suppress(DockerAPIError): with suppress(DockerAPIError):
await self.instance.install(self.latest_version, latest=True) await self.instance.install(
self.latest_version,
image=self.sys_updater.image_cli,
latest=True,
)
break break
_LOGGER.warning("Error on install cli plugin. Retry in 30sec") _LOGGER.warning("Error on install cli plugin. Retry in 30sec")
await asyncio.sleep(30) await asyncio.sleep(30)
_LOGGER.info("cli plugin now installed") _LOGGER.info("cli plugin now installed")
self.version = self.instance.version self.version = self.instance.version
self.image = self.instance.image self.image = self.sys_updater.image_cli
self.save_data() self.save_data()
async def update(self, version: Optional[str] = None) -> None: async def update(self, version: Optional[str] = None) -> None:
"""Update local HA cli.""" """Update local HA cli."""
version = version or self.latest_version version = version or self.latest_version
old_image = self.image
if version == self.version: if version == self.version:
_LOGGER.warning("Version %s is already installed for cli", version) _LOGGER.warning("Version %s is already installed for cli", version)
return return
try: try:
await self.instance.update(version, latest=True) await self.instance.update(
version, image=self.sys_updater.image_cli, latest=True
)
except DockerAPIError: except DockerAPIError:
_LOGGER.error("HA cli update fails") _LOGGER.error("HA cli update fails")
raise CliUpdateError() from None raise CliUpdateError() from None
else:
self.version = version
self.image = self.sys_updater.image_cli
self.save_data()
# Cleanup # Cleanup
with suppress(DockerAPIError): with suppress(DockerAPIError):
await self.instance.cleanup() await self.instance.cleanup(old_image=old_image)
self.version = version
self.save_data()
# Start cli # Start cli
await self.start() await self.start()

View File

@ -12,6 +12,7 @@ from .const import (
ATTR_LOGGING, ATTR_LOGGING,
ATTR_TIMEZONE, ATTR_TIMEZONE,
ATTR_WAIT_BOOT, ATTR_WAIT_BOOT,
ENV_SUPERVISOR_SHARE,
FILE_HASSIO_CONFIG, FILE_HASSIO_CONFIG,
SUPERVISOR_DATA, SUPERVISOR_DATA,
) )
@ -119,19 +120,19 @@ class CoreConfig(JsonConfig):
self._data[ATTR_LAST_BOOT] = value.isoformat() self._data[ATTR_LAST_BOOT] = value.isoformat()
@property @property
def path_hassio(self): def path_supervisor(self):
"""Return Supervisor data path.""" """Return Supervisor data path."""
return SUPERVISOR_DATA return SUPERVISOR_DATA
@property @property
def path_extern_hassio(self): def path_extern_supervisor(self):
"""Return Supervisor data path external for Docker.""" """Return Supervisor data path external for Docker."""
return PurePath(os.environ["SUPERVISOR_SHARE"]) return PurePath(os.environ[ENV_SUPERVISOR_SHARE])
@property @property
def path_extern_homeassistant(self): def path_extern_homeassistant(self):
"""Return config path external for Docker.""" """Return config path external for Docker."""
return str(PurePath(self.path_extern_hassio, HOMEASSISTANT_CONFIG)) return str(PurePath(self.path_extern_supervisor, HOMEASSISTANT_CONFIG))
@property @property
def path_homeassistant(self): def path_homeassistant(self):
@ -141,7 +142,7 @@ class CoreConfig(JsonConfig):
@property @property
def path_extern_ssl(self): def path_extern_ssl(self):
"""Return SSL path external for Docker.""" """Return SSL path external for Docker."""
return str(PurePath(self.path_extern_hassio, HASSIO_SSL)) return str(PurePath(self.path_extern_supervisor, HASSIO_SSL))
@property @property
def path_ssl(self): def path_ssl(self):
@ -166,7 +167,7 @@ class CoreConfig(JsonConfig):
@property @property
def path_extern_addons_local(self): def path_extern_addons_local(self):
"""Return path for custom Add-ons.""" """Return path for custom Add-ons."""
return PurePath(self.path_extern_hassio, ADDONS_LOCAL) return PurePath(self.path_extern_supervisor, ADDONS_LOCAL)
@property @property
def path_addons_data(self): def path_addons_data(self):
@ -176,7 +177,7 @@ class CoreConfig(JsonConfig):
@property @property
def path_extern_addons_data(self): def path_extern_addons_data(self):
"""Return root add-on data folder external for Docker.""" """Return root add-on data folder external for Docker."""
return PurePath(self.path_extern_hassio, ADDONS_DATA) return PurePath(self.path_extern_supervisor, ADDONS_DATA)
@property @property
def path_audio(self): def path_audio(self):
@ -186,7 +187,7 @@ class CoreConfig(JsonConfig):
@property @property
def path_extern_audio(self): def path_extern_audio(self):
"""Return root audio data folder external for Docker.""" """Return root audio data folder external for Docker."""
return PurePath(self.path_extern_hassio, AUDIO_DATA) return PurePath(self.path_extern_supervisor, AUDIO_DATA)
@property @property
def path_tmp(self): def path_tmp(self):
@ -196,7 +197,7 @@ class CoreConfig(JsonConfig):
@property @property
def path_extern_tmp(self): def path_extern_tmp(self):
"""Return Supervisor temp folder for Docker.""" """Return Supervisor temp folder for Docker."""
return PurePath(self.path_extern_hassio, TMP_DATA) return PurePath(self.path_extern_supervisor, TMP_DATA)
@property @property
def path_backup(self): def path_backup(self):
@ -206,7 +207,7 @@ class CoreConfig(JsonConfig):
@property @property
def path_extern_backup(self): def path_extern_backup(self):
"""Return root backup data folder external for Docker.""" """Return root backup data folder external for Docker."""
return PurePath(self.path_extern_hassio, BACKUP_DATA) return PurePath(self.path_extern_supervisor, BACKUP_DATA)
@property @property
def path_share(self): def path_share(self):
@ -221,12 +222,12 @@ class CoreConfig(JsonConfig):
@property @property
def path_extern_share(self): def path_extern_share(self):
"""Return root share data folder external for Docker.""" """Return root share data folder external for Docker."""
return PurePath(self.path_extern_hassio, SHARE_DATA) return PurePath(self.path_extern_supervisor, SHARE_DATA)
@property @property
def path_extern_dns(self): def path_extern_dns(self):
"""Return dns path external for Docker.""" """Return dns path external for Docker."""
return str(PurePath(self.path_extern_hassio, DNS_DATA)) return str(PurePath(self.path_extern_supervisor, DNS_DATA))
@property @property
def path_dns(self): def path_dns(self):

View File

@ -68,8 +68,14 @@ ENV_TOKEN_OLD = "HASSIO_TOKEN"
ENV_TOKEN = "SUPERVISOR_TOKEN" ENV_TOKEN = "SUPERVISOR_TOKEN"
ENV_TIME = "TZ" ENV_TIME = "TZ"
ENV_HOMEASSISTANT_REPOSITORY = "HOMEASSISTANT_REPOSITORY"
ENV_SUPERVISOR_SHARE = "SUPERVISOR_SHARE"
ENV_SUPERVISOR_NAME = "SUPERVISOR_NAME"
ENV_SUPERVISOR_MACHINE = "SUPERVISOR_MACHINE"
REQUEST_FROM = "HASSIO_FROM" REQUEST_FROM = "HASSIO_FROM"
ATTR_SUPERVISOR = "supervisor"
ATTR_MACHINE = "machine" ATTR_MACHINE = "machine"
ATTR_WAIT_BOOT = "wait_boot" ATTR_WAIT_BOOT = "wait_boot"
ATTR_DEPLOYMENT = "deployment" ATTR_DEPLOYMENT = "deployment"
@ -138,7 +144,6 @@ ATTR_USER = "user"
ATTR_SYSTEM = "system" ATTR_SYSTEM = "system"
ATTR_SNAPSHOTS = "snapshots" ATTR_SNAPSHOTS = "snapshots"
ATTR_HOMEASSISTANT = "homeassistant" ATTR_HOMEASSISTANT = "homeassistant"
ATTR_HASSIO = "hassio"
ATTR_HASSIO_API = "hassio_api" ATTR_HASSIO_API = "hassio_api"
ATTR_HOMEASSISTANT_API = "homeassistant_api" ATTR_HOMEASSISTANT_API = "homeassistant_api"
ATTR_UUID = "uuid" ATTR_UUID = "uuid"

View File

@ -203,10 +203,12 @@ class Core(CoreSysAttributes):
_LOGGER.info("Start repairing of Supervisor Environment") _LOGGER.info("Start repairing of Supervisor Environment")
await self.sys_run_in_executor(self.sys_docker.repair) await self.sys_run_in_executor(self.sys_docker.repair)
# Fix plugins
await asyncio.wait(
[self.sys_dns.repair(), self.sys_audio.repair(), self.sys_cli.repair()]
)
# Restore core functionality # Restore core functionality
await self.sys_dns.repair()
await self.sys_audio.repair()
await self.sys_cli.repair()
await self.sys_addons.repair() await self.sys_addons.repair()
await self.sys_homeassistant.repair() await self.sys_homeassistant.repair()

View File

@ -42,7 +42,8 @@ class CoreSys:
def __init__(self): def __init__(self):
"""Initialize coresys.""" """Initialize coresys."""
# Static attributes # Static attributes
self.machine_id: Optional[str] = None self._machine_id: Optional[str] = None
self._machine: Optional[str] = None
# External objects # External objects
self._loop: asyncio.BaseEventLoop = asyncio.get_running_loop() self._loop: asyncio.BaseEventLoop = asyncio.get_running_loop()
@ -81,13 +82,6 @@ class CoreSys:
self._discovery: Optional[Discovery] = None self._discovery: Optional[Discovery] = None
self._hwmonitor: Optional[HwMonitor] = None self._hwmonitor: Optional[HwMonitor] = None
@property
def machine(self) -> str:
"""Return running machine type of the Supervisor system."""
if self._homeassistant:
return self._homeassistant.machine
return None
@property @property
def dev(self) -> bool: def dev(self) -> bool:
"""Return True if we run dev mode.""" """Return True if we run dev mode."""
@ -397,6 +391,30 @@ class CoreSys:
raise RuntimeError("HassOS already set!") raise RuntimeError("HassOS already set!")
self._hassos = value self._hassos = value
@property
def machine(self) -> Optional[str]:
"""Return machine type string."""
return self._machine
@machine.setter
def machine(self, value: str):
"""Set a machine type string."""
if self._machine:
raise RuntimeError("Machine type already set!")
self._machine = value
@property
def machine_id(self) -> Optional[str]:
"""Return machine-id type string."""
return self._machine_id
@machine_id.setter
def machine_id(self, value: str):
"""Set a machine-id type string."""
if self._machine_id:
raise RuntimeError("Machine-ID type already set!")
self._machine_id = value
class CoreSysAttributes: class CoreSysAttributes:
"""Inheret basic CoreSysAttributes.""" """Inheret basic CoreSysAttributes."""

View File

@ -167,14 +167,16 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
if self.latest_version: if self.latest_version:
with suppress(DockerAPIError): with suppress(DockerAPIError):
await self.instance.install(self.latest_version) await self.instance.install(
self.latest_version, image=self.sys_updater.image_dns
)
break break
_LOGGER.warning("Error on install CoreDNS plugin. Retry in 30sec") _LOGGER.warning("Error on install CoreDNS plugin. Retry in 30sec")
await asyncio.sleep(30) await asyncio.sleep(30)
_LOGGER.info("CoreDNS plugin now installed") _LOGGER.info("CoreDNS plugin now installed")
self.version = self.instance.version self.version = self.instance.version
self.image = self.instance.image self.image = self.sys_updater.image_dns
self.save_data() self.save_data()
# Init Hosts # Init Hosts
@ -183,6 +185,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
async def update(self, version: Optional[str] = None) -> None: async def update(self, version: Optional[str] = None) -> None:
"""Update CoreDNS plugin.""" """Update CoreDNS plugin."""
version = version or self.latest_version version = version or self.latest_version
old_image = self.image
if version == self.version: if version == self.version:
_LOGGER.warning("Version %s is already installed for CoreDNS", version) _LOGGER.warning("Version %s is already installed for CoreDNS", version)
@ -190,17 +193,18 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
# Update # Update
try: try:
await self.instance.update(version) await self.instance.update(version, image=self.sys_updater.image_dns)
except DockerAPIError: except DockerAPIError:
_LOGGER.error("CoreDNS update fails") _LOGGER.error("CoreDNS update fails")
raise CoreDNSUpdateError() from None raise CoreDNSUpdateError() from None
else:
self.version = version
self.image = self.sys_updater.image_dns
self.save_data()
# Cleanup # Cleanup
with suppress(DockerAPIError): with suppress(DockerAPIError):
await self.instance.cleanup() await self.instance.cleanup(old_image=old_image)
self.version = version
self.save_data()
# Start CoreDNS # Start CoreDNS
await self.start() await self.start()

View File

@ -60,5 +60,5 @@ class DockerCli(DockerInterface, CoreSysAttributes):
"Start CLI %s with version %s - %s", "Start CLI %s with version %s - %s",
self.image, self.image,
self.version, self.version,
self.sys_docker.network.audio, self.sys_docker.network.cli,
) )

View File

@ -302,11 +302,11 @@ class DockerInterface(CoreSysAttributes):
_LOGGER.warning("Can't grep logs from %s: %s", self.image, err) _LOGGER.warning("Can't grep logs from %s: %s", self.image, err)
@process_lock @process_lock
def cleanup(self) -> 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."""
return self.sys_run_in_executor(self._cleanup) return self.sys_run_in_executor(self._cleanup, old_image)
def _cleanup(self) -> None: def _cleanup(self, old_image: Optional[str] = None) -> None:
"""Check if old version exists and cleanup. """Check if old version exists and cleanup.
Need run inside executor. Need run inside executor.
@ -317,6 +317,7 @@ class DockerInterface(CoreSysAttributes):
_LOGGER.warning("Can't find %s for cleanup", self.image) _LOGGER.warning("Can't find %s for cleanup", self.image)
raise DockerAPIError() from None raise DockerAPIError() from None
# Cleanup Current
for image in self.sys_docker.images.list(name=self.image): for image in self.sys_docker.images.list(name=self.image):
if origin.id == image.id: if origin.id == image.id:
continue continue
@ -325,6 +326,15 @@ class DockerInterface(CoreSysAttributes):
_LOGGER.info("Cleanup images: %s", image.tags) _LOGGER.info("Cleanup images: %s", image.tags)
self.sys_docker.images.remove(image.id, force=True) self.sys_docker.images.remove(image.id, force=True)
# Cleanup Old
if not old_image or self.image == old_image:
return
for image in self.sys_docker.images.list(name=old_image):
with suppress(docker.errors.DockerException):
_LOGGER.info("Cleanup images: %s", image.tags)
self.sys_docker.images.remove(image.id, force=True)
@process_lock @process_lock
def restart(self) -> Awaitable[None]: def restart(self) -> Awaitable[None]:
"""Restart docker container.""" """Restart docker container."""

View File

@ -4,7 +4,6 @@ from contextlib import asynccontextmanager, suppress
from datetime import datetime, timedelta from datetime import datetime, timedelta
from ipaddress import IPv4Address from ipaddress import IPv4Address
import logging import logging
import os
from pathlib import Path from pathlib import Path
import re import re
import secrets import secrets
@ -24,7 +23,6 @@ from .const import (
ATTR_AUDIO_OUTPUT, ATTR_AUDIO_OUTPUT,
ATTR_BOOT, ATTR_BOOT,
ATTR_IMAGE, ATTR_IMAGE,
ATTR_VERSION_LATEST,
ATTR_PORT, ATTR_PORT,
ATTR_REFRESH_TOKEN, ATTR_REFRESH_TOKEN,
ATTR_SSL, ATTR_SSL,
@ -166,20 +164,12 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
"""Return last available version of Home Assistant.""" """Return last available version of Home Assistant."""
return self.sys_updater.version_homeassistant return self.sys_updater.version_homeassistant
@latest_version.setter
def latest_version(self, value: str):
"""Set last available version of Home Assistant."""
if value:
self._data[ATTR_VERSION_LATEST] = value
else:
self._data.pop(ATTR_VERSION_LATEST, None)
@property @property
def image(self) -> str: 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 f"homeassistant/{self.sys_machine}-homeassistant"
@image.setter @image.setter
def image(self, value: str) -> None: def image(self, value: str) -> None:
@ -262,12 +252,15 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
_LOGGER.info("Setup HomeAssistant landingpage") _LOGGER.info("Setup HomeAssistant landingpage")
while True: while True:
try: try:
await self.instance.install("landingpage") await self.instance.install(
"landingpage", image=self.sys_updater.image_homeassistant
)
except DockerAPIError: except DockerAPIError:
_LOGGER.warning("Fails install landingpage, retry after 30sec") _LOGGER.warning("Fails install landingpage, retry after 30sec")
await asyncio.sleep(30) await asyncio.sleep(30)
else: else:
self.version = self.instance.version self.version = self.instance.version
self.image = self.sys_updater.image_homeassistant
self.save_data() self.save_data()
break break
@ -288,14 +281,16 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
tag = self.latest_version tag = self.latest_version
if tag: if tag:
with suppress(DockerAPIError): with suppress(DockerAPIError):
await self.instance.update(tag) await self.instance.update(
tag, image=self.sys_updater.image_homeassistant
)
break break
_LOGGER.warning("Error on install Home Assistant. Retry in 30sec") _LOGGER.warning("Error on install Home Assistant. Retry in 30sec")
await asyncio.sleep(30) await asyncio.sleep(30)
_LOGGER.info("Home Assistant docker now installed") _LOGGER.info("Home Assistant docker now installed")
self.version = self.instance.version self.version = self.instance.version
self.image = self.instance.image self.image = self.sys_updater.image_homeassistant
self.save_data() self.save_data()
# finishing # finishing
@ -313,6 +308,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
async def update(self, version: Optional[str] = None) -> None: async def update(self, version: Optional[str] = None) -> None:
"""Update HomeAssistant version.""" """Update HomeAssistant version."""
version = version or self.latest_version version = version or self.latest_version
old_image = self.image
rollback = self.version if not self.error_state else None rollback = self.version if not self.error_state else None
running = await self.instance.is_running() running = await self.instance.is_running()
exists = await self.instance.exists() exists = await self.instance.exists()
@ -326,20 +322,24 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
"""Run Home Assistant update.""" """Run Home Assistant update."""
_LOGGER.info("Update Home Assistant to version %s", to_version) _LOGGER.info("Update Home Assistant to version %s", to_version)
try: try:
await self.instance.update(to_version) await self.instance.update(
to_version, image=self.sys_updater.image_homeassistant
)
except DockerAPIError: except DockerAPIError:
_LOGGER.warning("Update Home Assistant image fails") _LOGGER.warning("Update Home Assistant image fails")
raise HomeAssistantUpdateError() from None raise HomeAssistantUpdateError() from None
else: else:
self.version = self.instance.version self.version = self.instance.version
self.image = self.sys_updater.image_homeassistant
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)
# Successfull - last step
self.save_data() self.save_data()
with suppress(DockerAPIError): with suppress(DockerAPIError):
await self.instance.cleanup() await self.instance.cleanup(old_image=old_image)
# Update Home Assistant # Update Home Assistant
with suppress(HomeAssistantError): with suppress(HomeAssistantError):

View File

@ -127,6 +127,11 @@ class Ingress(JsonConfig, CoreSysAttributes):
async def update_hass_panel(self, addon: Addon): async def update_hass_panel(self, addon: Addon):
"""Return True if Home Assistant up and running.""" """Return True if Home Assistant up and running."""
if not await self.sys_homeassistant.is_running():
_LOGGER.debug("Ignore panel update on Core")
return
# Update UI
method = "post" if addon.ingress_panel else "delete" method = "post" if addon.ingress_panel else "delete"
async with self.sys_homeassistant.make_request( async with self.sys_homeassistant.make_request(
method, f"api/hassio_push/panel/{addon.slug}" method, f"api/hassio_push/panel/{addon.slug}"

View File

@ -359,7 +359,7 @@ class Snapshot(CoreSysAttributes):
"""Internal function to snapshot a folder.""" """Internal function to snapshot a folder."""
slug_name = name.replace("/", "_") slug_name = name.replace("/", "_")
tar_name = Path(self._tmp.name, f"{slug_name}.tar.gz") tar_name = Path(self._tmp.name, f"{slug_name}.tar.gz")
origin_dir = Path(self.sys_config.path_hassio, name) origin_dir = Path(self.sys_config.path_supervisor, name)
# Check if exists # Check if exists
if not origin_dir.is_dir(): if not origin_dir.is_dir():
@ -396,7 +396,7 @@ class Snapshot(CoreSysAttributes):
"""Intenal function to restore a folder.""" """Intenal function to restore a folder."""
slug_name = name.replace("/", "_") slug_name = name.replace("/", "_")
tar_name = Path(self._tmp.name, f"{slug_name}.tar.gz") tar_name = Path(self._tmp.name, f"{slug_name}.tar.gz")
origin_dir = Path(self.sys_config.path_hassio, name) origin_dir = Path(self.sys_config.path_supervisor, name)
# Check if exists inside snapshot # Check if exists inside snapshot
if not tar_name.exists(): if not tar_name.exists():

View File

@ -65,7 +65,7 @@ class Supervisor(CoreSysAttributes):
@property @property
def latest_version(self) -> str: def latest_version(self) -> str:
"""Return last available version of Home Assistant.""" """Return last available version of Home Assistant."""
return self.sys_updater.version_hassio return self.sys_updater.version_supervisor
@property @property
def image(self) -> str: def image(self) -> str:
@ -115,7 +115,9 @@ class Supervisor(CoreSysAttributes):
_LOGGER.info("Update Supervisor to version %s", version) _LOGGER.info("Update Supervisor to version %s", version)
try: try:
await self.instance.install(version, image=None, latest=True) await self.instance.install(
version, image=self.sys_updater.image_supervisor, latest=True
)
except DockerAPIError: except DockerAPIError:
_LOGGER.error("Update of Supervisor fails!") _LOGGER.error("Update of Supervisor fails!")
raise SupervisorUpdateError() from None raise SupervisorUpdateError() from None

View File

@ -13,9 +13,10 @@ from .const import (
ATTR_CHANNEL, ATTR_CHANNEL,
ATTR_CLI, ATTR_CLI,
ATTR_DNS, ATTR_DNS,
ATTR_HASSIO, ATTR_SUPERVISOR,
ATTR_HASSOS, ATTR_HASSOS,
ATTR_HOMEASSISTANT, ATTR_HOMEASSISTANT,
ATTR_IMAGE,
FILE_HASSIO_UPDATER, FILE_HASSIO_UPDATER,
URL_HASSIO_VERSION, URL_HASSIO_VERSION,
UpdateChannels, UpdateChannels,
@ -53,9 +54,9 @@ class Updater(JsonConfig, CoreSysAttributes):
return self._data.get(ATTR_HOMEASSISTANT) return self._data.get(ATTR_HOMEASSISTANT)
@property @property
def version_hassio(self) -> Optional[str]: def version_supervisor(self) -> Optional[str]:
"""Return latest version of Supervisor.""" """Return latest version of Supervisor."""
return self._data.get(ATTR_HASSIO) return self._data.get(ATTR_SUPERVISOR)
@property @property
def version_hassos(self) -> Optional[str]: def version_hassos(self) -> Optional[str]:
@ -77,6 +78,51 @@ class Updater(JsonConfig, CoreSysAttributes):
"""Return latest version of Audio.""" """Return latest version of Audio."""
return self._data.get(ATTR_AUDIO) return self._data.get(ATTR_AUDIO)
@property
def image_homeassistant(self) -> Optional[str]:
"""Return latest version of Home Assistant."""
return (
self._data[ATTR_IMAGE]
.get(ATTR_HOMEASSISTANT, "")
.format(machine=self.sys_machine)
)
@property
def image_supervisor(self) -> Optional[str]:
"""Return latest version of Supervisor."""
return (
self._data[ATTR_IMAGE]
.get(ATTR_SUPERVISOR, "")
.format(arch=self.sys_arch.supervisor)
)
@property
def image_cli(self) -> Optional[str]:
"""Return latest version of CLI."""
return (
self._data[ATTR_IMAGE]
.get(ATTR_CLI, "")
.format(arch=self.sys_arch.supervisor)
)
@property
def image_dns(self) -> Optional[str]:
"""Return latest version of DNS."""
return (
self._data[ATTR_IMAGE]
.get(ATTR_DNS, "")
.format(arch=self.sys_arch.supervisor)
)
@property
def image_audio(self) -> Optional[str]:
"""Return latest version of Audio."""
return (
self._data[ATTR_IMAGE]
.get(ATTR_AUDIO, "")
.format(arch=self.sys_arch.supervisor)
)
@property @property
def channel(self) -> UpdateChannels: def channel(self) -> UpdateChannels:
"""Return upstream channel of Supervisor instance.""" """Return upstream channel of Supervisor instance."""
@ -116,7 +162,7 @@ class Updater(JsonConfig, CoreSysAttributes):
try: try:
# Update supervisor version # Update supervisor version
self._data[ATTR_HASSIO] = data["supervisor"] self._data[ATTR_SUPERVISOR] = data["supervisor"]
# Update Home Assistant core version # Update Home Assistant core version
self._data[ATTR_HOMEASSISTANT] = data["homeassistant"][machine] self._data[ATTR_HOMEASSISTANT] = data["homeassistant"][machine]
@ -130,6 +176,13 @@ class Updater(JsonConfig, CoreSysAttributes):
self._data[ATTR_DNS] = data["dns"] self._data[ATTR_DNS] = data["dns"]
self._data[ATTR_AUDIO] = data["audio"] self._data[ATTR_AUDIO] = data["audio"]
# Update images for that versions
self._data[ATTR_IMAGE][ATTR_HOMEASSISTANT] = data["image"]["core"]
self._data[ATTR_IMAGE][ATTR_SUPERVISOR] = data["image"]["supervisor"]
self._data[ATTR_IMAGE][ATTR_AUDIO] = data["image"]["audio"]
self._data[ATTR_IMAGE][ATTR_CLI] = data["image"]["cli"]
self._data[ATTR_IMAGE][ATTR_DNS] = data["image"]["dns"]
except KeyError as err: except KeyError as err:
_LOGGER.warning("Can't process version data: %s", err) _LOGGER.warning("Can't process version data: %s", err)
raise HassioUpdaterError() from None raise HassioUpdaterError() from None

View File

@ -17,7 +17,7 @@ from .const import (
ATTR_DEBUG, ATTR_DEBUG,
ATTR_DEBUG_BLOCK, ATTR_DEBUG_BLOCK,
ATTR_DNS, ATTR_DNS,
ATTR_HASSIO, ATTR_SUPERVISOR,
ATTR_HASSOS, ATTR_HASSOS,
ATTR_HOMEASSISTANT, ATTR_HOMEASSISTANT,
ATTR_IMAGE, ATTR_IMAGE,
@ -124,11 +124,21 @@ SCHEMA_UPDATER_CONFIG = vol.Schema(
UpdateChannels UpdateChannels
), ),
vol.Optional(ATTR_HOMEASSISTANT): vol.Coerce(str), vol.Optional(ATTR_HOMEASSISTANT): vol.Coerce(str),
vol.Optional(ATTR_HASSIO): vol.Coerce(str), vol.Optional(ATTR_SUPERVISOR): vol.Coerce(str),
vol.Optional(ATTR_HASSOS): vol.Coerce(str), vol.Optional(ATTR_HASSOS): vol.Coerce(str),
vol.Optional(ATTR_CLI): vol.Coerce(str), vol.Optional(ATTR_CLI): vol.Coerce(str),
vol.Optional(ATTR_DNS): vol.Coerce(str), vol.Optional(ATTR_DNS): vol.Coerce(str),
vol.Optional(ATTR_AUDIO): vol.Coerce(str), vol.Optional(ATTR_AUDIO): vol.Coerce(str),
vol.Optional(ATTR_IMAGE, default=dict): vol.Schema(
{
vol.Optional(ATTR_HOMEASSISTANT): docker_image,
vol.Optional(ATTR_SUPERVISOR): docker_image,
vol.Optional(ATTR_CLI): docker_image,
vol.Optional(ATTR_DNS): docker_image,
vol.Optional(ATTR_AUDIO): docker_image,
},
extra=vol.REMOVE_EXTRA,
),
}, },
extra=vol.REMOVE_EXTRA, extra=vol.REMOVE_EXTRA,
) )