From 86a7f11f64a02659b974ededb436f734c3d68aba Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 18 Apr 2017 12:29:43 +0200 Subject: [PATCH 1/9] Allow supervisor to update itself. --- API.md | 6 ++---- hassio/__main__.py | 3 +++ hassio/api/__init__.py | 4 ++-- hassio/api/supervisor.py | 13 +++++++------ hassio/const.py | 4 ++++ hassio/core.py | 8 +++++--- hassio/dock/supervisor.py | 33 +++++++++++++++++++++++++++++---- 7 files changed, 52 insertions(+), 19 deletions(-) diff --git a/API.md b/API.md index 7ed976be2a4..19be4c49930 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 26b21251c38..5f85f872f41 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 @@ 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 84ef3573b5a..681b7131e8e 100644 --- a/hassio/api/__init__.py +++ b/hassio/api/__init__.py @@ -41,10 +41,10 @@ def register_network(self, host_controll): 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 9ce407d7150..6b25f68ffaf 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 @@ 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 @@ async def options(self, request): 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: 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/const.py b/hassio/const.py index 1a35682a3b4..b1af2e5ef62 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -15,6 +15,8 @@ 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 @@ 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 ea9db2a05ea..a833ac72050 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 @@ def __init__(self, loop): # 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) @@ -63,7 +64,7 @@ async def setup(self): # 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 +105,12 @@ async def start(self): # 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/supervisor.py b/hassio/dock/supervisor.py index ed3f1aeed73..f1288453315 100644 --- a/hassio/dock/supervisor.py +++ b/hassio/dock/supervisor.py @@ -7,11 +7,40 @@ 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. + + Return a Future. + """ + if self._lock.locked(): + _LOGGER.error("Can't excute update while a task is in progress") + return False + + async with self._lock: + if await self.loop.run_in_executor(None, self._update, tag): + self.loop.create_task(self.hassio.stop(RESTART_EXIT_CODE)) + + def _update(self, tag): + """Update a docker image. + + Need run inside executor. + """ + _LOGGER.info("Update supervisor docker to %s:%s", self.image, tag) + + # update docker image + return self._install(tag): + async def run(self): """Run docker image.""" raise RuntimeError("Not support on supervisor docker container!") @@ -24,10 +53,6 @@ async def stop(self): """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!") From 14ee26ea29b375395ec545b3e32ad00f409e80f4 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 18 Apr 2017 12:34:15 +0200 Subject: [PATCH 2/9] fix lint --- hassio/dock/supervisor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hassio/dock/supervisor.py b/hassio/dock/supervisor.py index f1288453315..c456b85d588 100644 --- a/hassio/dock/supervisor.py +++ b/hassio/dock/supervisor.py @@ -39,7 +39,7 @@ def _update(self, tag): _LOGGER.info("Update supervisor docker to %s:%s", self.image, tag) # update docker image - return self._install(tag): + return self._install(tag) async def run(self): """Run docker image.""" From 454d82d985ca0ecc6692a90c629f74f735d54eb6 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 18 Apr 2017 12:35:32 +0200 Subject: [PATCH 3/9] Cleanups --- hassio/host_controll.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/hassio/host_controll.py b/hassio/host_controll.py index a6674009d5f..7685f9760cf 100644 --- a/hassio/host_controll.py +++ b/hassio/host_controll.py @@ -14,9 +14,8 @@ 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 @@ def host_update(self, version=None): 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") From c9876988da5cc8e8a3cdff79d5d8e70f4e4e6c38 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 18 Apr 2017 16:20:19 +0200 Subject: [PATCH 4/9] Add cleanup --- hassio/config.py | 15 +++++++++++++++ hassio/core.py | 1 + hassio/dock/__init__.py | 5 +---- hassio/dock/supervisor.py | 38 ++++++++++++++++++++++++++++---------- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/hassio/config.py b/hassio/config.py index 8f47f60790a..85fc51c4a6b 100644 --- a/hassio/config.py +++ b/hassio/config.py @@ -14,6 +14,7 @@ HASSIO_SSL = "{}/ssl" HASSIO_CURRENT = 'hassio_current' +HASSIO_CLEANUP = 'hassio_cleanup' ADDONS_REPO = "{}/addons" ADDONS_DATA = "{}/addons_data" @@ -87,6 +88,20 @@ def upstream_beta(self, value): """Set beta upstream mode.""" self._data[UPSTREAM_BETA] = bool(value) + @property + def cleanup_hassio(self): + """Return Version they need to cleanup.""" + return self._data.get(HASSIO_CLEANUP) + + @cleanup_hassio.setter + def cleanup_hassio(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/core.py b/hassio/core.py index a833ac72050..2587a7bb2e5 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -50,6 +50,7 @@ async def setup(self): """Setup HassIO orchestration.""" # supervisor await self.supervisor.attach() + await self.supervisor.cleanup() # hostcontroll host_info = await self.host_controll.info() diff --git a/hassio/dock/__init__.py b/hassio/dock/__init__.py index e4401fb74e8..07b25108a6e 100644 --- a/hassio/dock/__init__.py +++ b/hassio/dock/__init__.py @@ -209,10 +209,7 @@ def _remove(self): 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 c456b85d588..18a900fce62 100644 --- a/hassio/dock/supervisor.py +++ b/hassio/dock/supervisor.py @@ -1,6 +1,8 @@ """Init file for HassIO docker object.""" import os +import docker + from . import DockerBase @@ -19,27 +21,43 @@ def docker_name(self): return os.environ['SUPERVISOR_NAME'] async def update(self, tag): - """Update a supervisor docker image. - - Return a Future. - """ + """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._update, tag): + 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)) - def _update(self, tag): - """Update a docker image. + 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. """ - _LOGGER.info("Update supervisor docker to %s:%s", self.image, tag) + 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 - # update docker image - return self._install(tag) + return True async def run(self): """Run docker image.""" From 0d867af79fcf3c30233ab60ec5abba6f321c8ea4 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 18 Apr 2017 16:24:33 +0200 Subject: [PATCH 5/9] Fix lint --- hassio/dock/supervisor.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hassio/dock/supervisor.py b/hassio/dock/supervisor.py index 18a900fce62..41c463338ff 100644 --- a/hassio/dock/supervisor.py +++ b/hassio/dock/supervisor.py @@ -1,9 +1,13 @@ """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): From 6c217d506c449b226c2f129565def730f85b59f5 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 18 Apr 2017 16:28:20 +0200 Subject: [PATCH 6/9] update version --- hassio/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hassio/const.py b/hassio/const.py index b1af2e5ef62..2dc2897c7c8 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' From 094c5968f4cd9f69d9f08868d744820ba5eef101 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 18 Apr 2017 17:18:32 +0200 Subject: [PATCH 7/9] Fix cleanup name --- hassio/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hassio/config.py b/hassio/config.py index 85fc51c4a6b..f7fa29b3df8 100644 --- a/hassio/config.py +++ b/hassio/config.py @@ -89,12 +89,12 @@ def upstream_beta(self, value): self._data[UPSTREAM_BETA] = bool(value) @property - def cleanup_hassio(self): + def hassio_cleanup(self): """Return Version they need to cleanup.""" return self._data.get(HASSIO_CLEANUP) - @cleanup_hassio.setter - def cleanup_hassio(self, version): + @hassio_cleanup.setter + def hassio_cleanup(self, version): """Set or remove cleanup flag.""" if version is None: self._data.pop(HASSIO_CLEANUP, None) From f231d54daae8219e45d96612fecd2f72d9719279 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 18 Apr 2017 17:46:21 +0200 Subject: [PATCH 8/9] Use supervisor version --- hassio/api/supervisor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hassio/api/supervisor.py b/hassio/api/supervisor.py index 6b25f68ffaf..02434ec8443 100644 --- a/hassio/api/supervisor.py +++ b/hassio/api/supervisor.py @@ -62,7 +62,7 @@ async def update(self, request): 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 asyncio.shield(self.supervisor.update(version)) From e1028d6eca8d9da5b67f3ad04494d1429b1bacdd Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 18 Apr 2017 17:47:28 +0200 Subject: [PATCH 9/9] update version --- version.json | 2 +- version_beta.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/version.json b/version.json index 02eb23bdd67..e768d292e4d 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 02eb23bdd67..e768d292e4d 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"