diff --git a/hassio/__main__.py b/hassio/__main__.py index 788ab64e2..01d82b714 100644 --- a/hassio/__main__.py +++ b/hassio/__main__.py @@ -23,7 +23,7 @@ if __name__ == "__main__": loop.run_until_complete(hassio.setup()) _LOGGER.info("Start Hassio task") - loop.create_task(hassio.start()) + loop.call_soon_threadsafe(asyncio.ensure_future, hassio.start(), loop) try: loop.add_signal_handler( @@ -32,4 +32,5 @@ if __name__ == "__main__": _LOGGER.warning("Could not bind to SIGTERM") loop.run_forever() + loop.close() _LOGGER.info("Close Hassio") diff --git a/hassio/api/__init__.py b/hassio/api/__init__.py index cde4ae8ef..1bd0d577a 100644 --- a/hassio/api/__init__.py +++ b/hassio/api/__init__.py @@ -44,6 +44,7 @@ class RestAPI(object): """Register supervisor function.""" api_supervisor = APISupervisor(self.config, self.loop, host_controll) + 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/update', api_supervisor.update) self.webapp.router.add_get( diff --git a/hassio/api/homeassistant.py b/hassio/api/homeassistant.py index f4ad3841f..cd55a2453 100644 --- a/hassio/api/homeassistant.py +++ b/hassio/api/homeassistant.py @@ -2,11 +2,17 @@ import asyncio import logging -from .util import api_process, json_loads +import voluptuous as vol + +from .util import api_process, api_validate from ..const import ATTR_VERSION, ATTR_CURRENT _LOGGER = logging.getLogger(__name__) +SCHEMA_VERSION = vol.Schema({ + vol.Optional(ATTR_VERSION): vol.Coerce(str), +}) + class APIHomeAssistant(object): """Handle rest api for homeassistant functions.""" @@ -30,7 +36,7 @@ class APIHomeAssistant(object): @api_process async def update(self, request): """Update host OS.""" - body = await request.json(loads=json_loads) + body = await api_validate(SCHEMA_VERSION, request) version = body.get(ATTR_VERSION, self.config.current_homeassistant) if self.dock_hass.in_progress: diff --git a/hassio/api/host.py b/hassio/api/host.py index 5fc0c0fa4..23f0fb453 100644 --- a/hassio/api/host.py +++ b/hassio/api/host.py @@ -1,11 +1,19 @@ """Init file for HassIO host rest api.""" import logging -from .util import api_process_hostcontroll, json_loads +import voluptuous as vol + +from .util import api_process_hostcontroll, api_process, api_validate from ..const import ATTR_VERSION _LOGGER = logging.getLogger(__name__) +UNKNOWN = 'unknown' + +SCHEMA_VERSION = vol.Schema({ + vol.Optional(ATTR_VERSION): vol.Coerce(str), +}) + class APIHost(object): """Handle rest api for host functions.""" @@ -16,10 +24,20 @@ class APIHost(object): self.loop = loop self.host_controll = host_controll - @api_process_hostcontroll - def info(self, request): + @api_process + async def info(self, request): """Return host information.""" - return self.host_controll.info() + if not self.host_controll.active: + info = { + 'os': UNKNOWN, + 'version': UNKNOWN, + 'current': UNKNOWN, + 'level': 0, + 'hostname': UNKNOWN, + } + return info + + return await self.host_controll.info() @api_process_hostcontroll def reboot(self, request): @@ -31,20 +49,10 @@ class APIHost(object): """Poweroff host.""" return self.host_controll.shutdown() - @api_process_hostcontroll - def network_info(self, request): - """Edit network settings.""" - pass - - @api_process_hostcontroll - def network_update(self, request): - """Edit network settings.""" - pass - @api_process_hostcontroll async def update(self, request): """Update host OS.""" - body = await request.json(loads=json_loads) + body = await api_validate(SCHEMA_VERSION, request) version = body.get(ATTR_VERSION) if version == self.host_controll.version: diff --git a/hassio/api/supervisor.py b/hassio/api/supervisor.py index 62925b1fe..73ef6ae5b 100644 --- a/hassio/api/supervisor.py +++ b/hassio/api/supervisor.py @@ -1,11 +1,22 @@ """Init file for HassIO supervisor rest api.""" import logging -from .util import api_process, api_process_hostcontroll, json_loads +import voluptuous as vol + +from .util import api_process, api_process_hostcontroll, api_validate from ..const import ATTR_VERSION, ATTR_CURRENT, ATTR_BETA, HASSIO_VERSION _LOGGER = logging.getLogger(__name__) +SCHEMA_OPTIONS = vol.Schema({ + # pylint: disable=no-value-for-parameter + vol.Optional(ATTR_BETA): vol.Boolean(), +}) + +SCHEMA_VERSION = vol.Schema({ + vol.Optional(ATTR_VERSION): vol.Coerce(str), +}) + class APISupervisor(object): """Handle rest api for supervisor functions.""" @@ -16,6 +27,11 @@ class APISupervisor(object): self.loop = loop self.host_controll = host_controll + @api_process + async def ping(self, request): + """Return ok for signal that the api is ready.""" + return True + @api_process async def info(self, request): """Return host information.""" @@ -30,7 +46,7 @@ class APISupervisor(object): @api_process async def options(self, request): """Set supervisor options.""" - body = await request.json(loads=json_loads) + body = await api_validate(SCHEMA_OPTIONS, request) if ATTR_BETA in body: self.config.upstream_beta = body[ATTR_BETA] @@ -40,7 +56,7 @@ class APISupervisor(object): @api_process_hostcontroll async def update(self, request): """Update host OS.""" - body = await request.json(loads=json_loads) + body = await api_validate(SCHEMA_VERSION, request) version = body.get(ATTR_VERSION, self.config.current_hassio) if version == HASSIO_VERSION: diff --git a/hassio/api/util.py b/hassio/api/util.py index c1225400b..fd75d5ec2 100644 --- a/hassio/api/util.py +++ b/hassio/api/util.py @@ -4,6 +4,8 @@ import logging from aiohttp import web from aiohttp.web_exceptions import HTTPServiceUnavailable +import voluptuous as vol +from voluptuous.humanize import humanize_error from ..const import ( JSON_RESULT, JSON_DATA, JSON_MESSAGE, RESULT_OK, RESULT_ERROR) @@ -52,7 +54,7 @@ def api_process_hostcontroll(method): if isinstance(answer, dict): return api_return_ok(data=answer) elif answer is None: - return api_not_supported() + return api_return_error("Function is not supported") elif answer: return api_return_ok() return api_return_error() @@ -72,10 +74,16 @@ def api_return_ok(data=None): """Return a API ok answer.""" return web.json_response({ JSON_RESULT: RESULT_OK, - JSON_DATA: data, + JSON_DATA: data or {}, }) -def api_not_supported(): - """Return a api error with not supported.""" - return api_return_error("Function is not supported") +async def api_validate(schema, request): + """Validate request data with schema.""" + data = await request.json(loads=json_loads) + try: + schema(data) + except vol.Invalid as ex: + raise RuntimeError(humanize_error(data, ex)) from None + + return data diff --git a/hassio/const.py b/hassio/const.py index a531b3c68..e641ea88a 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -1,5 +1,5 @@ """Const file for HassIO.""" -HASSIO_VERSION = '0.4' +HASSIO_VERSION = '0.5' URL_HASSIO_VERSION = \ 'https://raw.githubusercontent.com/pvizeli/hassio/master/version.json' diff --git a/setup.py b/setup.py index 7927bbf33..31a39092c 100644 --- a/setup.py +++ b/setup.py @@ -36,5 +36,6 @@ setup( 'aiohttp', 'docker', 'colorlog', + 'voluptuous', ] )