Merge pull request #4 from pvizeli/selfupdate

Allow supervisor to update itself.
This commit is contained in:
Pascal Vizeli 2017-04-18 17:48:55 +02:00 committed by GitHub
commit 71590f90ae
12 changed files with 97 additions and 39 deletions

6
API.md
View File

@ -11,7 +11,6 @@ Communicate over unix socket with a host daemon.
# reboot # reboot
# shutdown # shutdown
# host-update [v] # host-update [v]
# supervisor-update [v]
# network info # network info
# network hostname xy # network hostname xy
@ -24,9 +23,8 @@ Communicate over unix socket with a host daemon.
level: level:
- 1: power functions - 1: power functions
- 2: supervisor update - 2: host update
- 4: host update - 4: network functions
- 8: network functions
Answer: Answer:
``` ```

View File

@ -2,6 +2,7 @@
import asyncio import asyncio
import logging import logging
import signal import signal
import sys
import hassio.bootstrap as bootstrap import hassio.bootstrap as bootstrap
import hassio.core as core import hassio.core as core
@ -33,4 +34,6 @@ if __name__ == "__main__":
loop.run_forever() loop.run_forever()
loop.close() loop.close()
_LOGGER.info("Close Hassio") _LOGGER.info("Close Hassio")
sys.exit(hassio.exit_code)

View File

@ -41,10 +41,10 @@ class RestAPI(object):
self.webapp.router.add_get('/network/info', api_net.info) self.webapp.router.add_get('/network/info', api_net.info)
self.webapp.router.add_get('/network/options', api_net.options) 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.""" """Register supervisor function."""
api_supervisor = APISupervisor( 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/ping', api_supervisor.ping)
self.webapp.router.add_get('/supervisor/info', api_supervisor.info) self.webapp.router.add_get('/supervisor/info', api_supervisor.info)

View File

@ -1,9 +1,10 @@
"""Init file for HassIO supervisor rest api.""" """Init file for HassIO supervisor rest api."""
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
from .util import api_process, api_process_hostcontroll, api_validate from .util import api_process, api_validate
from ..const import ( from ..const import (
ATTR_ADDONS, ATTR_VERSION, ATTR_CURRENT, ATTR_BETA, HASSIO_VERSION) ATTR_ADDONS, ATTR_VERSION, ATTR_CURRENT, ATTR_BETA, HASSIO_VERSION)
@ -22,11 +23,11 @@ SCHEMA_VERSION = vol.Schema({
class APISupervisor(object): class APISupervisor(object):
"""Handle rest api for supervisor functions.""" """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.""" """Initialize supervisor rest api part."""
self.config = config self.config = config
self.loop = loop self.loop = loop
self.host_controll = host_controll self.supervisor = supervisor
self.addons = addons self.addons = addons
@api_process @api_process
@ -55,13 +56,13 @@ class APISupervisor(object):
return self.config.save() return self.config.save()
@api_process_hostcontroll @api_process
async def update(self, request): async def update(self, request):
"""Update host OS.""" """Update supervisor OS."""
body = await api_validate(SCHEMA_VERSION, request) body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.config.current_hassio) 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") raise RuntimeError("Version is already in use")
return await self.host_controll.supervisor_update(version=version) return await asyncio.shield(self.supervisor.update(version))

View File

@ -14,6 +14,7 @@ HOMEASSISTANT_CURRENT = 'homeassistant_current'
HASSIO_SSL = "{}/ssl" HASSIO_SSL = "{}/ssl"
HASSIO_CURRENT = 'hassio_current' HASSIO_CURRENT = 'hassio_current'
HASSIO_CLEANUP = 'hassio_cleanup'
ADDONS_REPO = "{}/addons" ADDONS_REPO = "{}/addons"
ADDONS_DATA = "{}/addons_data" ADDONS_DATA = "{}/addons_data"
@ -87,6 +88,20 @@ class CoreConfig(Config):
"""Set beta upstream mode.""" """Set beta upstream mode."""
self._data[UPSTREAM_BETA] = bool(value) 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 @property
def homeassistant_image(self): def homeassistant_image(self):
"""Return docker homeassistant repository.""" """Return docker homeassistant repository."""

View File

@ -1,5 +1,5 @@
"""Const file for HassIO.""" """Const file for HassIO."""
HASSIO_VERSION = '0.6' HASSIO_VERSION = '0.7'
URL_HASSIO_VERSION = \ URL_HASSIO_VERSION = \
'https://raw.githubusercontent.com/pvizeli/hassio/master/version.json' 'https://raw.githubusercontent.com/pvizeli/hassio/master/version.json'
@ -15,6 +15,8 @@ HASSIO_SHARE = "/data"
RUN_UPDATE_INFO_TASKS = 28800 RUN_UPDATE_INFO_TASKS = 28800
RUN_RELOAD_ADDONS_TASKS = 28800 RUN_RELOAD_ADDONS_TASKS = 28800
RESTART_EXIT_CODE = 100
FILE_HASSIO_ADDONS = "{}/addons.json".format(HASSIO_SHARE) FILE_HASSIO_ADDONS = "{}/addons.json".format(HASSIO_SHARE)
FILE_HASSIO_CONFIG = "{}/config.json".format(HASSIO_SHARE) FILE_HASSIO_CONFIG = "{}/config.json".format(HASSIO_SHARE)
@ -49,7 +51,9 @@ ATTR_IMAGE = 'image'
STARTUP_BEFORE = 'before' STARTUP_BEFORE = 'before'
STARTUP_AFTER = 'after' STARTUP_AFTER = 'after'
STARTUP_ONCE = 'once' STARTUP_ONCE = 'once'
BOOT_AUTO = 'auto' BOOT_AUTO = 'auto'
BOOT_MANUAL = 'manual' BOOT_MANUAL = 'manual'
STATE_STARTED = 'started' STATE_STARTED = 'started'
STATE_STOPPED = 'stopped' STATE_STOPPED = 'stopped'

View File

@ -25,6 +25,7 @@ class HassIO(object):
def __init__(self, loop): def __init__(self, loop):
"""Initialize hassio object.""" """Initialize hassio object."""
self.exit_code = 0
self.loop = loop self.loop = loop
self.websession = aiohttp.ClientSession(loop=self.loop) self.websession = aiohttp.ClientSession(loop=self.loop)
self.config = bootstrap.initialize_system_data(self.websession) self.config = bootstrap.initialize_system_data(self.websession)
@ -35,7 +36,7 @@ class HassIO(object):
# init basic docker container # init basic docker container
self.supervisor = DockerSupervisor( self.supervisor = DockerSupervisor(
self.config, self.loop, self.dock) self.config, self.loop, self.dock, self)
self.homeassistant = DockerHomeAssistant( self.homeassistant = DockerHomeAssistant(
self.config, self.loop, self.dock) self.config, self.loop, self.dock)
@ -49,6 +50,7 @@ class HassIO(object):
"""Setup HassIO orchestration.""" """Setup HassIO orchestration."""
# supervisor # supervisor
await self.supervisor.attach() await self.supervisor.attach()
await self.supervisor.cleanup()
# hostcontroll # hostcontroll
host_info = await self.host_controll.info() host_info = await self.host_controll.info()
@ -63,7 +65,7 @@ class HassIO(object):
# rest api views # rest api views
self.api.register_host(self.host_controll) self.api.register_host(self.host_controll)
self.api.register_network(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_homeassistant(self.homeassistant)
self.api.register_addons(self.addons) self.api.register_addons(self.addons)
@ -104,11 +106,12 @@ class HassIO(object):
# start addon mark as after # start addon mark as after
await self.addons.auto_boot(STARTUP_AFTER) await self.addons.auto_boot(STARTUP_AFTER)
async def stop(self): async def stop(self, exit_code=0):
"""Stop a running orchestration.""" """Stop a running orchestration."""
tasks = [self.websession.close(), self.api.stop()] tasks = [self.websession.close(), self.api.stop()]
await asyncio.wait(tasks, loop=self.loop) await asyncio.wait(tasks, loop=self.loop)
self.exit_code = exit_code
self.loop.stop() self.loop.stop()
async def _setup_homeassistant(self): async def _setup_homeassistant(self):

View File

@ -209,10 +209,7 @@ class DockerBase(object):
return True return True
async def update(self, tag): async def update(self, tag):
"""Update a docker image. """Update a docker image."""
Return a Future.
"""
if self._lock.locked(): if self._lock.locked():
_LOGGER.error("Can't excute update while a task is in progress") _LOGGER.error("Can't excute update while a task is in progress")
return False return False

View File

@ -1,17 +1,68 @@
"""Init file for HassIO docker object.""" """Init file for HassIO docker object."""
import logging
import os import os
import docker
from . import DockerBase from . import DockerBase
from ..const import RESTART_EXIT_CODE
_LOGGER = logging.getLogger(__name__)
class DockerSupervisor(DockerBase): class DockerSupervisor(DockerBase):
"""Docker hassio wrapper for HomeAssistant.""" """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 @property
def docker_name(self): def docker_name(self):
"""Return name of docker container.""" """Return name of docker container."""
return os.environ['SUPERVISOR_NAME'] 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): async def run(self):
"""Run docker image.""" """Run docker image."""
raise RuntimeError("Not support on supervisor docker container!") raise RuntimeError("Not support on supervisor docker container!")
@ -24,10 +75,6 @@ class DockerSupervisor(DockerBase):
"""Stop/remove docker container.""" """Stop/remove docker container."""
raise RuntimeError("Not support on supervisor 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): async def remove(self):
"""Remove docker image.""" """Remove docker image."""
raise RuntimeError("Not support on supervisor docker container!") raise RuntimeError("Not support on supervisor docker container!")

View File

@ -14,9 +14,8 @@ _LOGGER = logging.getLogger(__name__)
TIMEOUT = 15 TIMEOUT = 15
LEVEL_POWER = 1 LEVEL_POWER = 1
LEVEL_UPDATE_SUPERVISOR = 2 LEVEL_UPDATE_HOST = 2
LEVEL_UPDATE_HOST = 4 LEVEL_NETWORK = 4
LEVEL_NETWORK = 8
class HostControll(object): class HostControll(object):
@ -101,12 +100,3 @@ class HostControll(object):
if version: if version:
return self._send_command("host-update {}".format(version)) return self._send_command("host-update {}".format(version))
return self._send_command("host-update") 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")

View File

@ -1,5 +1,5 @@
{ {
"hassio_tag": "0.6", "hassio_tag": "0.7",
"homeassistant_tag": "0.42.3", "homeassistant_tag": "0.42.3",
"resinos_version": "0.3", "resinos_version": "0.3",
"resinhup_version": "0.1" "resinhup_version": "0.1"

View File

@ -1,5 +1,5 @@
{ {
"hassio_tag": "0.6", "hassio_tag": "0.7",
"homeassistant_tag": "0.42.3", "homeassistant_tag": "0.42.3",
"resinos_version": "0.3", "resinos_version": "0.3",
"resinhup_version": "0.1" "resinhup_version": "0.1"