diff --git a/API.md b/API.md index 7ed976be2..19be4c499 100644 --- a/API.md +++ b/API.md @@ -11,7 +11,6 @@ Communicate over unix socket with a host daemon. # reboot # shutdown # host-update [v] -# supervisor-update [v] # network info # network hostname xy @@ -24,9 +23,8 @@ Communicate over unix socket with a host daemon. level: - 1: power functions -- 2: supervisor update -- 4: host update -- 8: network functions +- 2: host update +- 4: network functions Answer: ``` diff --git a/hassio/__main__.py b/hassio/__main__.py index 26b21251c..5f85f872f 100644 --- a/hassio/__main__.py +++ b/hassio/__main__.py @@ -2,6 +2,7 @@ import asyncio import logging import signal +import sys import hassio.bootstrap as bootstrap import hassio.core as core @@ -33,4 +34,6 @@ if __name__ == "__main__": loop.run_forever() loop.close() + _LOGGER.info("Close Hassio") + sys.exit(hassio.exit_code) diff --git a/hassio/api/__init__.py b/hassio/api/__init__.py index 84ef3573b..681b7131e 100644 --- a/hassio/api/__init__.py +++ b/hassio/api/__init__.py @@ -41,10 +41,10 @@ class RestAPI(object): self.webapp.router.add_get('/network/info', api_net.info) self.webapp.router.add_get('/network/options', api_net.options) - def register_supervisor(self, host_controll, addons): + def register_supervisor(self, supervisor, addons): """Register supervisor function.""" api_supervisor = APISupervisor( - self.config, self.loop, host_controll, addons) + self.config, self.loop, supervisor, addons) self.webapp.router.add_get('/supervisor/ping', api_supervisor.ping) self.webapp.router.add_get('/supervisor/info', api_supervisor.info) diff --git a/hassio/api/supervisor.py b/hassio/api/supervisor.py index 9ce407d71..02434ec84 100644 --- a/hassio/api/supervisor.py +++ b/hassio/api/supervisor.py @@ -1,9 +1,10 @@ """Init file for HassIO supervisor rest api.""" +import asyncio import logging import voluptuous as vol -from .util import api_process, api_process_hostcontroll, api_validate +from .util import api_process, api_validate from ..const import ( ATTR_ADDONS, ATTR_VERSION, ATTR_CURRENT, ATTR_BETA, HASSIO_VERSION) @@ -22,11 +23,11 @@ SCHEMA_VERSION = vol.Schema({ class APISupervisor(object): """Handle rest api for supervisor functions.""" - def __init__(self, config, loop, host_controll, addons): + def __init__(self, config, loop, supervisor, addons): """Initialize supervisor rest api part.""" self.config = config self.loop = loop - self.host_controll = host_controll + self.supervisor = supervisor self.addons = addons @api_process @@ -55,13 +56,13 @@ class APISupervisor(object): return self.config.save() - @api_process_hostcontroll + @api_process async def update(self, request): - """Update host OS.""" + """Update supervisor OS.""" body = await api_validate(SCHEMA_VERSION, request) version = body.get(ATTR_VERSION, self.config.current_hassio) - if version == HASSIO_VERSION: + if version == self.supervisor.version: raise RuntimeError("Version is already in use") - return await self.host_controll.supervisor_update(version=version) + return await asyncio.shield(self.supervisor.update(version)) diff --git a/hassio/config.py b/hassio/config.py index 8f47f6079..f7fa29b3d 100644 --- a/hassio/config.py +++ b/hassio/config.py @@ -14,6 +14,7 @@ HOMEASSISTANT_CURRENT = 'homeassistant_current' HASSIO_SSL = "{}/ssl" HASSIO_CURRENT = 'hassio_current' +HASSIO_CLEANUP = 'hassio_cleanup' ADDONS_REPO = "{}/addons" ADDONS_DATA = "{}/addons_data" @@ -87,6 +88,20 @@ class CoreConfig(Config): """Set beta upstream mode.""" self._data[UPSTREAM_BETA] = bool(value) + @property + def hassio_cleanup(self): + """Return Version they need to cleanup.""" + return self._data.get(HASSIO_CLEANUP) + + @hassio_cleanup.setter + def hassio_cleanup(self, version): + """Set or remove cleanup flag.""" + if version is None: + self._data.pop(HASSIO_CLEANUP, None) + else: + self._data[HASSIO_CLEANUP] = version + self.save() + @property def homeassistant_image(self): """Return docker homeassistant repository.""" diff --git a/hassio/const.py b/hassio/const.py index 1a35682a3..2dc2897c7 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -1,5 +1,5 @@ """Const file for HassIO.""" -HASSIO_VERSION = '0.6' +HASSIO_VERSION = '0.7' URL_HASSIO_VERSION = \ 'https://raw.githubusercontent.com/pvizeli/hassio/master/version.json' @@ -15,6 +15,8 @@ HASSIO_SHARE = "/data" RUN_UPDATE_INFO_TASKS = 28800 RUN_RELOAD_ADDONS_TASKS = 28800 +RESTART_EXIT_CODE = 100 + FILE_HASSIO_ADDONS = "{}/addons.json".format(HASSIO_SHARE) FILE_HASSIO_CONFIG = "{}/config.json".format(HASSIO_SHARE) @@ -49,7 +51,9 @@ ATTR_IMAGE = 'image' STARTUP_BEFORE = 'before' STARTUP_AFTER = 'after' STARTUP_ONCE = 'once' + BOOT_AUTO = 'auto' BOOT_MANUAL = 'manual' + STATE_STARTED = 'started' STATE_STOPPED = 'stopped' diff --git a/hassio/core.py b/hassio/core.py index ea9db2a05..2587a7bb2 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -25,6 +25,7 @@ class HassIO(object): def __init__(self, loop): """Initialize hassio object.""" + self.exit_code = 0 self.loop = loop self.websession = aiohttp.ClientSession(loop=self.loop) self.config = bootstrap.initialize_system_data(self.websession) @@ -35,7 +36,7 @@ class HassIO(object): # init basic docker container self.supervisor = DockerSupervisor( - self.config, self.loop, self.dock) + self.config, self.loop, self.dock, self) self.homeassistant = DockerHomeAssistant( self.config, self.loop, self.dock) @@ -49,6 +50,7 @@ class HassIO(object): """Setup HassIO orchestration.""" # supervisor await self.supervisor.attach() + await self.supervisor.cleanup() # hostcontroll host_info = await self.host_controll.info() @@ -63,7 +65,7 @@ class HassIO(object): # rest api views self.api.register_host(self.host_controll) self.api.register_network(self.host_controll) - self.api.register_supervisor(self.host_controll, self.addons) + self.api.register_supervisor(self.supervisor, self.addons) self.api.register_homeassistant(self.homeassistant) self.api.register_addons(self.addons) @@ -104,11 +106,12 @@ class HassIO(object): # start addon mark as after await self.addons.auto_boot(STARTUP_AFTER) - async def stop(self): + async def stop(self, exit_code=0): """Stop a running orchestration.""" tasks = [self.websession.close(), self.api.stop()] await asyncio.wait(tasks, loop=self.loop) + self.exit_code = exit_code self.loop.stop() async def _setup_homeassistant(self): diff --git a/hassio/dock/__init__.py b/hassio/dock/__init__.py index e4401fb74..07b25108a 100644 --- a/hassio/dock/__init__.py +++ b/hassio/dock/__init__.py @@ -209,10 +209,7 @@ class DockerBase(object): return True async def update(self, tag): - """Update a docker image. - - Return a Future. - """ + """Update a docker image.""" if self._lock.locked(): _LOGGER.error("Can't excute update while a task is in progress") return False diff --git a/hassio/dock/supervisor.py b/hassio/dock/supervisor.py index ed3f1aeed..41c463338 100644 --- a/hassio/dock/supervisor.py +++ b/hassio/dock/supervisor.py @@ -1,17 +1,68 @@ """Init file for HassIO docker object.""" +import logging import os +import docker + from . import DockerBase +from ..const import RESTART_EXIT_CODE + +_LOGGER = logging.getLogger(__name__) class DockerSupervisor(DockerBase): """Docker hassio wrapper for HomeAssistant.""" + def __init__(self, config, loop, dock, hassio, image=None): + """Initialize docker base wrapper.""" + super().__init__(config, loop, dock, image=image) + + self.hassio = hassio + @property def docker_name(self): """Return name of docker container.""" return os.environ['SUPERVISOR_NAME'] + async def update(self, tag): + """Update a supervisor docker image.""" + if self._lock.locked(): + _LOGGER.error("Can't excute update while a task is in progress") + return False + + _LOGGER.info("Update supervisor docker to %s:%s", self.image, tag) + old_version = self.version + + async with self._lock: + if await self.loop.run_in_executor(None, self._install, tag): + self.config.hassio_cleanup = old_version + self.loop.create_task(self.hassio.stop(RESTART_EXIT_CODE)) + + async def cleanup(self): + """Check if old supervisor version exists and cleanup.""" + if not self.config.hassio_cleanup: + return + + async with self._lock: + if await self.loop.run_in_executor(None, self._cleanup): + self.config.hassio_cleanup = None + + def _cleanup(self): + """Remove old image. + + Need run inside executor. + """ + old_image = "{}:{}".format(self.image, self.config.hassio_cleanup) + + _LOGGER.info("Old supervisor docker found %s", old_image) + try: + self.dock.images.remove(image=old_image, force=True) + except docker.errors.DockerException as err: + _LOGGER.warning("Can't remove old image %s -> %s", old_image, err) + return False + + return True + async def run(self): """Run docker image.""" raise RuntimeError("Not support on supervisor docker container!") @@ -24,10 +75,6 @@ class DockerSupervisor(DockerBase): """Stop/remove docker container.""" raise RuntimeError("Not support on supervisor docker container!") - async def update(self, tag): - """Update docker image.""" - raise RuntimeError("Not support on supervisor docker container!") - async def remove(self): """Remove docker image.""" raise RuntimeError("Not support on supervisor docker container!") diff --git a/hassio/host_controll.py b/hassio/host_controll.py index a6674009d..7685f9760 100644 --- a/hassio/host_controll.py +++ b/hassio/host_controll.py @@ -14,9 +14,8 @@ _LOGGER = logging.getLogger(__name__) TIMEOUT = 15 LEVEL_POWER = 1 -LEVEL_UPDATE_SUPERVISOR = 2 -LEVEL_UPDATE_HOST = 4 -LEVEL_NETWORK = 8 +LEVEL_UPDATE_HOST = 2 +LEVEL_NETWORK = 4 class HostControll(object): @@ -101,12 +100,3 @@ class HostControll(object): if version: return self._send_command("host-update {}".format(version)) return self._send_command("host-update") - - def supervisor_update(self, version=None): - """Update the supervisor on host system. - - Return a coroutine. - """ - if version: - return self._send_command("supervisor-update {}".format(version)) - return self._send_command("supervisor-update") diff --git a/version.json b/version.json index 02eb23bdd..e768d292e 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "hassio_tag": "0.6", + "hassio_tag": "0.7", "homeassistant_tag": "0.42.3", "resinos_version": "0.3", "resinhup_version": "0.1" diff --git a/version_beta.json b/version_beta.json index 02eb23bdd..e768d292e 100644 --- a/version_beta.json +++ b/version_beta.json @@ -1,5 +1,5 @@ { - "hassio_tag": "0.6", + "hassio_tag": "0.7", "homeassistant_tag": "0.42.3", "resinos_version": "0.3", "resinhup_version": "0.1"