diff --git a/API.md b/API.md index 2ad3dfc34a9..ad93c8f309c 100644 --- a/API.md +++ b/API.md @@ -62,6 +62,10 @@ Optional: Reload addons/version. +- `/supervisor/logs` + +Output the raw docker log + ### Host - `/host/shutdown` @@ -123,6 +127,10 @@ Optional: } ``` +- `/homeassistant/logs` + +Output the raw docker log + ### REST API addons - `/addons/{addon}/info` @@ -163,6 +171,10 @@ Optional: } ``` +- `/addons/{addon}/logs` + +Output the raw docker log + ## Host Controll Communicate over unix socket with a host daemon. diff --git a/README.md b/README.md index 352de2642f9..0135745b3fe 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ It is a docker image (supervisor) they manage HomeAssistant docker and give a in ## Feature in progress - Backup/Restore -- Read docker logs and extend to api - MQTT addon - DHCP-Server addon diff --git a/hassio/addons/__init__.py b/hassio/addons/__init__.py index 1e6d06c24f1..7b10c99a1ee 100644 --- a/hassio/addons/__init__.py +++ b/hassio/addons/__init__.py @@ -36,6 +36,7 @@ async def prepare(self, arch): for addon in self.list_installed: self.dockers[addon] = DockerAddon( self.config, self.loop, self.dock, self, addon) + await self.dockers[addon].attach() async def reload(self): """Update addons from repo and reload list.""" @@ -144,10 +145,6 @@ async def stop(self, addon): async def update(self, addon, version=None): """Update addon.""" - if not self.is_installed(addon): - _LOGGER.error("Addon %s is not installed", addon) - return False - if addon not in self.dockers: _LOGGER.error("No docker found for addon %s", addon) return False @@ -157,3 +154,11 @@ async def update(self, addon, version=None): self.set_version(addon, version) return True return False + + async def logs(self, addon): + """Return addons log output.""" + if addon not in self.dockers: + _LOGGER.error("No docker found for addon %s", addon) + return False + + return await self.dockers[addon].logs() diff --git a/hassio/api/__init__.py b/hassio/api/__init__.py index 5919ac8f6df..d345f6b3586 100644 --- a/hassio/api/__init__.py +++ b/hassio/api/__init__.py @@ -52,6 +52,7 @@ def register_supervisor(self, supervisor, addons): self.webapp.router.add_get('/supervisor/reload', api_supervisor.reload) self.webapp.router.add_get( '/supervisor/options', api_supervisor.options) + self.webapp.router.add_get('/supervisor/logs', api_supervisor.logs) def register_homeassistant(self, dock_homeassistant): """Register homeassistant function.""" @@ -59,6 +60,7 @@ def register_homeassistant(self, dock_homeassistant): self.webapp.router.add_get('/homeassistant/info', api_hass.info) self.webapp.router.add_get('/homeassistant/update', api_hass.update) + self.webapp.router.add_get('/homeassistant/logs', api_hass.logs) def register_addons(self, addons): """Register homeassistant function.""" @@ -74,6 +76,7 @@ def register_addons(self, addons): self.webapp.router.add_get('/addons/{addon}/update', api_addons.update) self.webapp.router.add_get( '/addons/{addon}/options', api_addons.options) + self.webapp.router.add_get('/addons/{addon}/logs', api_addons.logs) async def start(self): """Run rest api webserver.""" diff --git a/hassio/api/addons.py b/hassio/api/addons.py index 6904550826a..8b4f3bf0ea6 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -5,7 +5,7 @@ import voluptuous as vol from voluptuous.humanize import humanize_error -from .util import api_process, api_validate +from .util import api_process, api_process_raw, api_validate from ..const import ( ATTR_VERSION, ATTR_CURRENT, ATTR_STATE, ATTR_BOOT, ATTR_OPTIONS, STATE_STOPPED, STATE_STARTED) @@ -124,3 +124,9 @@ async def update(self, request): return await asyncio.shield( self.addons.update(addon, version), loop=self.loop) + + @api_process_raw + def logs(self, request): + """Return logs from addon.""" + addon = self._extract_addon(request) + return self.addons.logs(addon) diff --git a/hassio/api/homeassistant.py b/hassio/api/homeassistant.py index 6a81e66f63a..21b2d2fc16a 100644 --- a/hassio/api/homeassistant.py +++ b/hassio/api/homeassistant.py @@ -4,7 +4,7 @@ import voluptuous as vol -from .util import api_process, api_validate +from .util import api_process, api_process_raw, api_validate from ..const import ATTR_VERSION, ATTR_CURRENT _LOGGER = logging.getLogger(__name__) @@ -17,17 +17,17 @@ class APIHomeAssistant(object): """Handle rest api for homeassistant functions.""" - def __init__(self, config, loop, dock_hass): + def __init__(self, config, loop, homeassistant): """Initialize homeassistant rest api part.""" self.config = config self.loop = loop - self.dock_hass = dock_hass + self.homeassistant = homeassistant @api_process async def info(self, request): """Return host information.""" info = { - ATTR_VERSION: self.dock_hass.version, + ATTR_VERSION: self.homeassistant.version, ATTR_CURRENT: self.config.current_homeassistant, } @@ -39,11 +39,19 @@ async def update(self, request): body = await api_validate(SCHEMA_VERSION, request) version = body.get(ATTR_VERSION, self.config.current_homeassistant) - if self.dock_hass.in_progress: + if self.homeassistant.in_progress: raise RuntimeError("Other task is in progress") - if version == self.dock_hass.version: + if version == self.homeassistant.version: raise RuntimeError("Version is already in use") return await asyncio.shield( - self.dock_hass.update(version), loop=self.loop) + self.homeassistant.update(version), loop=self.loop) + + @api_process_raw + def logs(self, request): + """Return homeassistant docker logs. + + Return a coroutine. + """ + return self.homeassistant.logs() diff --git a/hassio/api/supervisor.py b/hassio/api/supervisor.py index 6d0ed2ec6e1..d6e89cf2f13 100644 --- a/hassio/api/supervisor.py +++ b/hassio/api/supervisor.py @@ -4,7 +4,7 @@ import voluptuous as vol -from .util import api_process, api_validate +from .util import api_process, api_process_raw, api_validate from ..const import ( ATTR_ADDONS, ATTR_VERSION, ATTR_CURRENT, ATTR_BETA, HASSIO_VERSION) @@ -80,3 +80,11 @@ async def reload(self, request): raise RuntimeError("Some reload task fails!") return True + + @api_process_raw + def logs(self, request): + """Return supervisor docker logs. + + Return a coroutine. + """ + return self.supervisor.logs() diff --git a/hassio/api/util.py b/hassio/api/util.py index e435da684ae..54a82676572 100644 --- a/hassio/api/util.py +++ b/hassio/api/util.py @@ -62,6 +62,20 @@ async def wrap_hostcontroll(api, *args, **kwargs): return wrap_hostcontroll +def api_process_raw(method): + """Wrap function with raw output to rest api.""" + async def wrap_api(api, *args, **kwargs): + """Return api information.""" + try: + message = await method(api, *args, **kwargs) + except RuntimeError as err: + message = str(err).encode() + + return web.Response(body=message) + + return wrap_api + + def api_return_error(message=None): """Return a API error message.""" return web.json_response({ diff --git a/hassio/const.py b/hassio/const.py index 2c632270378..a200e74052f 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -1,5 +1,5 @@ """Const file for HassIO.""" -HASSIO_VERSION = '0.11' +HASSIO_VERSION = '0.12' URL_HASSIO_VERSION = \ 'https://raw.githubusercontent.com/pvizeli/hassio/master/version.json' diff --git a/hassio/dock/__init__.py b/hassio/dock/__init__.py index 9ab3f44bb99..94184e038cd 100644 --- a/hassio/dock/__init__.py +++ b/hassio/dock/__init__.py @@ -99,8 +99,9 @@ def _is_running(self): self.container.attrs['Config']['Env']) except docker.errors.DockerException: return False + else: + self.container.reload() - self.container.reload() return self.container.status == 'running' async def attach(self): @@ -243,3 +244,25 @@ def _update(self, tag): return True return False + + async def logs(self): + """Return docker logs of container.""" + if self._lock.locked(): + _LOGGER.error("Can't excute logs while a task is in progress") + return False + + async with self._lock: + return await self.loop.run_in_executor(None, self._logs) + + def _logs(self): + """Return docker logs of container. + + Need run inside executor. + """ + if not self.container: + return + + try: + return self.container.logs(tail=100, stdout=True, stderr=True) + except docker.errors.DockerException as err: + _LOGGER.warning("Can't grap logs from %s -> %s", self.image, err) diff --git a/hassio/dock/addon.py b/hassio/dock/addon.py index 408908ce0b2..64b9d41d260 100644 --- a/hassio/dock/addon.py +++ b/hassio/dock/addon.py @@ -74,3 +74,17 @@ def _run(self): return False return True + + def _attach(self): + """Attach to running docker container. + + Need run inside executor. + """ + try: + self.container = self.dock.containers.get(self.docker_name) + self.version = get_version_from_env( + self.container.attrs['Config']['Env']) + _LOGGER.info("Attach to image %s with version %s", + self.image, self.version) + except (docker.errors.DockerException, KeyError): + pass