diff --git a/supervisor/api/supervisor.py b/supervisor/api/supervisor.py index 55e37225d..33b71613b 100644 --- a/supervisor/api/supervisor.py +++ b/supervisor/api/supervisor.py @@ -92,7 +92,7 @@ class APISupervisor(CoreSysAttributes): return { 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_ARCH: self.sys_supervisor.arch, ATTR_IP_ADDRESS: str(self.sys_supervisor.ip_address), @@ -153,7 +153,7 @@ class APISupervisor(CoreSysAttributes): async def update(self, request: web.Request) -> None: """Update Supervisor OS.""" 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: raise APIError("Version {} is already in use".format(version)) diff --git a/supervisor/audio.py b/supervisor/audio.py index 14374857b..d98c740d4 100644 --- a/supervisor/audio.py +++ b/supervisor/audio.py @@ -128,36 +128,40 @@ class Audio(JsonConfig, CoreSysAttributes): if self.latest_version: with suppress(DockerAPIError): - await self.instance.install(self.latest_version) + await self.instance.install( + self.latest_version, image=self.sys_updater.image_audio + ) break _LOGGER.warning("Error on install Audio plugin. Retry in 30sec") await asyncio.sleep(30) _LOGGER.info("Audio plugin now installed") self.version = self.instance.version - self.image = self.instance.image + self.image = self.sys_updater.image_audio self.save_data() async def update(self, version: Optional[str] = None) -> None: """Update Audio plugin.""" version = version or self.latest_version + old_image = self.image if version == self.version: _LOGGER.warning("Version %s is already installed for Audio", version) return try: - await self.instance.update(version) + await self.instance.update(version, image=self.sys_updater.image_audio) except DockerAPIError: _LOGGER.error("Audio update fails") raise AudioUpdateError() from None else: - # Cleanup - with suppress(DockerAPIError): - await self.instance.cleanup() + self.version = version + self.image = self.sys_updater.image_audio + self.save_data() - self.version = version - self.save_data() + # Cleanup + with suppress(DockerAPIError): + await self.instance.cleanup(old_image=old_image) # Start Audio await self.start() diff --git a/supervisor/bootstrap.py b/supervisor/bootstrap.py index 26ae31352..6c23e68e2 100644 --- a/supervisor/bootstrap.py +++ b/supervisor/bootstrap.py @@ -12,7 +12,14 @@ from .api import RestAPI from .arch import CpuArch from .auth import Auth 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 .cli import HaCli from .coresys import CoreSys @@ -35,9 +42,6 @@ from .utils.dt import fetch_timezone _LOGGER: logging.Logger = logging.getLogger(__name__) -ENV_SHARE = "SUPERVISOR_SHARE" -ENV_NAME = "SUPERVISOR_NAME" -ENV_REPO = "HOMEASSISTANT_REPOSITORY" MACHINE_ID = Path("/etc/machine-id") @@ -81,6 +85,13 @@ async def initialize_coresys(): if coresys.config.timezone == "UTC": 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 @@ -165,7 +176,7 @@ def migrate_system_env(coresys: CoreSys): config = coresys.config # 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(): try: old_build.rmdir() @@ -202,12 +213,20 @@ def initialize_logging(): def check_environment() -> None: """Check if all environment are exists.""" # check environment variables - for key in (ENV_SHARE, ENV_NAME, ENV_REPO): + for key in (ENV_SUPERVISOR_SHARE, ENV_SUPERVISOR_NAME): try: os.environ[key] except KeyError: _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 if not SOCKET_DOCKER.is_socket(): _LOGGER.fatal("Can't find Docker socket!") diff --git a/supervisor/cli.py b/supervisor/cli.py index 80cf955c1..af7171166 100644 --- a/supervisor/cli.py +++ b/supervisor/cli.py @@ -102,36 +102,44 @@ class HaCli(CoreSysAttributes, JsonConfig): if self.latest_version: 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 _LOGGER.warning("Error on install cli plugin. Retry in 30sec") await asyncio.sleep(30) _LOGGER.info("cli plugin now installed") self.version = self.instance.version - self.image = self.instance.image + self.image = self.sys_updater.image_cli self.save_data() async def update(self, version: Optional[str] = None) -> None: """Update local HA cli.""" version = version or self.latest_version + old_image = self.image if version == self.version: _LOGGER.warning("Version %s is already installed for cli", version) return try: - await self.instance.update(version, latest=True) + await self.instance.update( + version, image=self.sys_updater.image_cli, latest=True + ) except DockerAPIError: _LOGGER.error("HA cli update fails") raise CliUpdateError() from None + else: + self.version = version + self.image = self.sys_updater.image_cli + self.save_data() # Cleanup with suppress(DockerAPIError): - await self.instance.cleanup() - - self.version = version - self.save_data() + await self.instance.cleanup(old_image=old_image) # Start cli await self.start() diff --git a/supervisor/config.py b/supervisor/config.py index d13222848..e4fb771d5 100644 --- a/supervisor/config.py +++ b/supervisor/config.py @@ -12,6 +12,7 @@ from .const import ( ATTR_LOGGING, ATTR_TIMEZONE, ATTR_WAIT_BOOT, + ENV_SUPERVISOR_SHARE, FILE_HASSIO_CONFIG, SUPERVISOR_DATA, ) @@ -119,19 +120,19 @@ class CoreConfig(JsonConfig): self._data[ATTR_LAST_BOOT] = value.isoformat() @property - def path_hassio(self): + def path_supervisor(self): """Return Supervisor data path.""" return SUPERVISOR_DATA @property - def path_extern_hassio(self): + def path_extern_supervisor(self): """Return Supervisor data path external for Docker.""" - return PurePath(os.environ["SUPERVISOR_SHARE"]) + return PurePath(os.environ[ENV_SUPERVISOR_SHARE]) @property def path_extern_homeassistant(self): """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 def path_homeassistant(self): @@ -141,7 +142,7 @@ class CoreConfig(JsonConfig): @property def path_extern_ssl(self): """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 def path_ssl(self): @@ -166,7 +167,7 @@ class CoreConfig(JsonConfig): @property def path_extern_addons_local(self): """Return path for custom Add-ons.""" - return PurePath(self.path_extern_hassio, ADDONS_LOCAL) + return PurePath(self.path_extern_supervisor, ADDONS_LOCAL) @property def path_addons_data(self): @@ -176,7 +177,7 @@ class CoreConfig(JsonConfig): @property def path_extern_addons_data(self): """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 def path_audio(self): @@ -186,7 +187,7 @@ class CoreConfig(JsonConfig): @property def path_extern_audio(self): """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 def path_tmp(self): @@ -196,7 +197,7 @@ class CoreConfig(JsonConfig): @property def path_extern_tmp(self): """Return Supervisor temp folder for Docker.""" - return PurePath(self.path_extern_hassio, TMP_DATA) + return PurePath(self.path_extern_supervisor, TMP_DATA) @property def path_backup(self): @@ -206,7 +207,7 @@ class CoreConfig(JsonConfig): @property def path_extern_backup(self): """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 def path_share(self): @@ -221,12 +222,12 @@ class CoreConfig(JsonConfig): @property def path_extern_share(self): """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 def path_extern_dns(self): """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 def path_dns(self): diff --git a/supervisor/const.py b/supervisor/const.py index c4067278f..ce64a018f 100644 --- a/supervisor/const.py +++ b/supervisor/const.py @@ -68,8 +68,14 @@ ENV_TOKEN_OLD = "HASSIO_TOKEN" ENV_TOKEN = "SUPERVISOR_TOKEN" 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" +ATTR_SUPERVISOR = "supervisor" ATTR_MACHINE = "machine" ATTR_WAIT_BOOT = "wait_boot" ATTR_DEPLOYMENT = "deployment" @@ -138,7 +144,6 @@ ATTR_USER = "user" ATTR_SYSTEM = "system" ATTR_SNAPSHOTS = "snapshots" ATTR_HOMEASSISTANT = "homeassistant" -ATTR_HASSIO = "hassio" ATTR_HASSIO_API = "hassio_api" ATTR_HOMEASSISTANT_API = "homeassistant_api" ATTR_UUID = "uuid" diff --git a/supervisor/core.py b/supervisor/core.py index 77d0ee5a1..e73938c45 100644 --- a/supervisor/core.py +++ b/supervisor/core.py @@ -203,10 +203,12 @@ class Core(CoreSysAttributes): _LOGGER.info("Start repairing of Supervisor Environment") 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 - await self.sys_dns.repair() - await self.sys_audio.repair() - await self.sys_cli.repair() await self.sys_addons.repair() await self.sys_homeassistant.repair() diff --git a/supervisor/coresys.py b/supervisor/coresys.py index 6015df87c..510e6ff70 100644 --- a/supervisor/coresys.py +++ b/supervisor/coresys.py @@ -42,7 +42,8 @@ class CoreSys: def __init__(self): """Initialize coresys.""" # Static attributes - self.machine_id: Optional[str] = None + self._machine_id: Optional[str] = None + self._machine: Optional[str] = None # External objects self._loop: asyncio.BaseEventLoop = asyncio.get_running_loop() @@ -81,13 +82,6 @@ class CoreSys: self._discovery: Optional[Discovery] = 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 def dev(self) -> bool: """Return True if we run dev mode.""" @@ -397,6 +391,30 @@ class CoreSys: raise RuntimeError("HassOS already set!") 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: """Inheret basic CoreSysAttributes.""" diff --git a/supervisor/dns.py b/supervisor/dns.py index 516fe2ed3..8cadde70a 100644 --- a/supervisor/dns.py +++ b/supervisor/dns.py @@ -167,14 +167,16 @@ class CoreDNS(JsonConfig, CoreSysAttributes): if self.latest_version: with suppress(DockerAPIError): - await self.instance.install(self.latest_version) + await self.instance.install( + self.latest_version, image=self.sys_updater.image_dns + ) break _LOGGER.warning("Error on install CoreDNS plugin. Retry in 30sec") await asyncio.sleep(30) _LOGGER.info("CoreDNS plugin now installed") self.version = self.instance.version - self.image = self.instance.image + self.image = self.sys_updater.image_dns self.save_data() # Init Hosts @@ -183,6 +185,7 @@ class CoreDNS(JsonConfig, CoreSysAttributes): async def update(self, version: Optional[str] = None) -> None: """Update CoreDNS plugin.""" version = version or self.latest_version + old_image = self.image if version == self.version: _LOGGER.warning("Version %s is already installed for CoreDNS", version) @@ -190,17 +193,18 @@ class CoreDNS(JsonConfig, CoreSysAttributes): # Update try: - await self.instance.update(version) + await self.instance.update(version, image=self.sys_updater.image_dns) except DockerAPIError: _LOGGER.error("CoreDNS update fails") raise CoreDNSUpdateError() from None + else: + self.version = version + self.image = self.sys_updater.image_dns + self.save_data() # Cleanup with suppress(DockerAPIError): - await self.instance.cleanup() - - self.version = version - self.save_data() + await self.instance.cleanup(old_image=old_image) # Start CoreDNS await self.start() diff --git a/supervisor/docker/cli.py b/supervisor/docker/cli.py index 08866b46e..0f9fe6b27 100644 --- a/supervisor/docker/cli.py +++ b/supervisor/docker/cli.py @@ -60,5 +60,5 @@ class DockerCli(DockerInterface, CoreSysAttributes): "Start CLI %s with version %s - %s", self.image, self.version, - self.sys_docker.network.audio, + self.sys_docker.network.cli, ) diff --git a/supervisor/docker/interface.py b/supervisor/docker/interface.py index ba0c5c54a..8a6819d74 100644 --- a/supervisor/docker/interface.py +++ b/supervisor/docker/interface.py @@ -302,11 +302,11 @@ class DockerInterface(CoreSysAttributes): _LOGGER.warning("Can't grep logs from %s: %s", self.image, err) @process_lock - def cleanup(self) -> Awaitable[None]: + def cleanup(self, old_image: Optional[str] = None) -> Awaitable[None]: """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. Need run inside executor. @@ -317,6 +317,7 @@ class DockerInterface(CoreSysAttributes): _LOGGER.warning("Can't find %s for cleanup", self.image) raise DockerAPIError() from None + # Cleanup Current for image in self.sys_docker.images.list(name=self.image): if origin.id == image.id: continue @@ -325,6 +326,15 @@ class DockerInterface(CoreSysAttributes): _LOGGER.info("Cleanup images: %s", image.tags) 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 def restart(self) -> Awaitable[None]: """Restart docker container.""" diff --git a/supervisor/homeassistant.py b/supervisor/homeassistant.py index 62bf9581e..c9fc68f77 100644 --- a/supervisor/homeassistant.py +++ b/supervisor/homeassistant.py @@ -4,7 +4,6 @@ from contextlib import asynccontextmanager, suppress from datetime import datetime, timedelta from ipaddress import IPv4Address import logging -import os from pathlib import Path import re import secrets @@ -24,7 +23,6 @@ from .const import ( ATTR_AUDIO_OUTPUT, ATTR_BOOT, ATTR_IMAGE, - ATTR_VERSION_LATEST, ATTR_PORT, ATTR_REFRESH_TOKEN, ATTR_SSL, @@ -166,20 +164,12 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): """Return last available version of Home Assistant.""" 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 def image(self) -> str: """Return image name of the Home Assistant container.""" if self._data.get(ATTR_IMAGE): return self._data[ATTR_IMAGE] - return os.environ["HOMEASSISTANT_REPOSITORY"] + return f"homeassistant/{self.sys_machine}-homeassistant" @image.setter def image(self, value: str) -> None: @@ -262,12 +252,15 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): _LOGGER.info("Setup HomeAssistant landingpage") while True: try: - await self.instance.install("landingpage") + await self.instance.install( + "landingpage", image=self.sys_updater.image_homeassistant + ) except DockerAPIError: _LOGGER.warning("Fails install landingpage, retry after 30sec") await asyncio.sleep(30) else: self.version = self.instance.version + self.image = self.sys_updater.image_homeassistant self.save_data() break @@ -288,14 +281,16 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): tag = self.latest_version if tag: with suppress(DockerAPIError): - await self.instance.update(tag) + await self.instance.update( + tag, image=self.sys_updater.image_homeassistant + ) break _LOGGER.warning("Error on install Home Assistant. Retry in 30sec") await asyncio.sleep(30) _LOGGER.info("Home Assistant docker now installed") self.version = self.instance.version - self.image = self.instance.image + self.image = self.sys_updater.image_homeassistant self.save_data() # finishing @@ -313,6 +308,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): async def update(self, version: Optional[str] = None) -> None: """Update HomeAssistant version.""" version = version or self.latest_version + old_image = self.image rollback = self.version if not self.error_state else None running = await self.instance.is_running() exists = await self.instance.exists() @@ -326,20 +322,24 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): """Run Home Assistant update.""" _LOGGER.info("Update Home Assistant to version %s", to_version) try: - await self.instance.update(to_version) + await self.instance.update( + to_version, image=self.sys_updater.image_homeassistant + ) except DockerAPIError: _LOGGER.warning("Update Home Assistant image fails") raise HomeAssistantUpdateError() from None else: self.version = self.instance.version + self.image = self.sys_updater.image_homeassistant if running: await self._start() - _LOGGER.info("Successful run Home Assistant %s", to_version) + + # Successfull - last step self.save_data() with suppress(DockerAPIError): - await self.instance.cleanup() + await self.instance.cleanup(old_image=old_image) # Update Home Assistant with suppress(HomeAssistantError): diff --git a/supervisor/ingress.py b/supervisor/ingress.py index f6fad670f..1c7ef85e1 100644 --- a/supervisor/ingress.py +++ b/supervisor/ingress.py @@ -127,6 +127,11 @@ class Ingress(JsonConfig, CoreSysAttributes): async def update_hass_panel(self, addon: Addon): """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" async with self.sys_homeassistant.make_request( method, f"api/hassio_push/panel/{addon.slug}" diff --git a/supervisor/snapshots/snapshot.py b/supervisor/snapshots/snapshot.py index bb74af991..abbdc3919 100644 --- a/supervisor/snapshots/snapshot.py +++ b/supervisor/snapshots/snapshot.py @@ -359,7 +359,7 @@ class Snapshot(CoreSysAttributes): """Internal function to snapshot a folder.""" slug_name = name.replace("/", "_") 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 if not origin_dir.is_dir(): @@ -396,7 +396,7 @@ class Snapshot(CoreSysAttributes): """Intenal function to restore a folder.""" slug_name = name.replace("/", "_") 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 if not tar_name.exists(): diff --git a/supervisor/supervisor.py b/supervisor/supervisor.py index ee5be07b1..59f5ca426 100644 --- a/supervisor/supervisor.py +++ b/supervisor/supervisor.py @@ -65,7 +65,7 @@ class Supervisor(CoreSysAttributes): @property def latest_version(self) -> str: """Return last available version of Home Assistant.""" - return self.sys_updater.version_hassio + return self.sys_updater.version_supervisor @property def image(self) -> str: @@ -115,7 +115,9 @@ class Supervisor(CoreSysAttributes): _LOGGER.info("Update Supervisor to version %s", version) 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: _LOGGER.error("Update of Supervisor fails!") raise SupervisorUpdateError() from None diff --git a/supervisor/updater.py b/supervisor/updater.py index 47ac77001..cffe86e84 100644 --- a/supervisor/updater.py +++ b/supervisor/updater.py @@ -13,9 +13,10 @@ from .const import ( ATTR_CHANNEL, ATTR_CLI, ATTR_DNS, - ATTR_HASSIO, + ATTR_SUPERVISOR, ATTR_HASSOS, ATTR_HOMEASSISTANT, + ATTR_IMAGE, FILE_HASSIO_UPDATER, URL_HASSIO_VERSION, UpdateChannels, @@ -53,9 +54,9 @@ class Updater(JsonConfig, CoreSysAttributes): return self._data.get(ATTR_HOMEASSISTANT) @property - def version_hassio(self) -> Optional[str]: + def version_supervisor(self) -> Optional[str]: """Return latest version of Supervisor.""" - return self._data.get(ATTR_HASSIO) + return self._data.get(ATTR_SUPERVISOR) @property def version_hassos(self) -> Optional[str]: @@ -77,6 +78,51 @@ class Updater(JsonConfig, CoreSysAttributes): """Return latest version of 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 def channel(self) -> UpdateChannels: """Return upstream channel of Supervisor instance.""" @@ -116,7 +162,7 @@ class Updater(JsonConfig, CoreSysAttributes): try: # Update supervisor version - self._data[ATTR_HASSIO] = data["supervisor"] + self._data[ATTR_SUPERVISOR] = data["supervisor"] # Update Home Assistant core version self._data[ATTR_HOMEASSISTANT] = data["homeassistant"][machine] @@ -130,6 +176,13 @@ class Updater(JsonConfig, CoreSysAttributes): self._data[ATTR_DNS] = data["dns"] 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: _LOGGER.warning("Can't process version data: %s", err) raise HassioUpdaterError() from None diff --git a/supervisor/validate.py b/supervisor/validate.py index e7a28c199..ceb45a6ec 100644 --- a/supervisor/validate.py +++ b/supervisor/validate.py @@ -17,7 +17,7 @@ from .const import ( ATTR_DEBUG, ATTR_DEBUG_BLOCK, ATTR_DNS, - ATTR_HASSIO, + ATTR_SUPERVISOR, ATTR_HASSOS, ATTR_HOMEASSISTANT, ATTR_IMAGE, @@ -124,11 +124,21 @@ SCHEMA_UPDATER_CONFIG = vol.Schema( UpdateChannels ), 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_CLI): vol.Coerce(str), vol.Optional(ATTR_DNS): 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, )