Allow supervisor to update itself.

This commit is contained in:
pvizeli 2017-04-18 12:29:43 +02:00
parent 78d1e1d9e7
commit 86a7f11f64
7 changed files with 52 additions and 19 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 == HASSIO_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

@ -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)
@ -63,7 +64,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 +105,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

@ -7,11 +7,40 @@ from . import DockerBase
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.
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): 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 +53,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!")