mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-09 18:26:30 +00:00
Extend API to view logs from docker (#11)
* Extend API to view logs from docker * Pump version * Fix lint * Change to raw api output * Fix aiohttp response * Fix aiohttp response p2 * Fix body convert * Add attach to docker addon
This commit is contained in:
parent
98f0ff2a01
commit
f99c6335c0
12
API.md
12
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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -36,6 +36,7 @@ class AddonManager(AddonsData):
|
||||
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 @@ class AddonManager(AddonsData):
|
||||
|
||||
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 @@ class AddonManager(AddonsData):
|
||||
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()
|
||||
|
@ -52,6 +52,7 @@ class RestAPI(object):
|
||||
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 @@ class RestAPI(object):
|
||||
|
||||
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 @@ class RestAPI(object):
|
||||
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."""
|
||||
|
@ -5,7 +5,7 @@ import logging
|
||||
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 @@ class APIAddons(object):
|
||||
|
||||
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)
|
||||
|
@ -4,7 +4,7 @@ import logging
|
||||
|
||||
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 @@ SCHEMA_VERSION = vol.Schema({
|
||||
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 @@ class APIHomeAssistant(object):
|
||||
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()
|
||||
|
@ -4,7 +4,7 @@ import logging
|
||||
|
||||
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 @@ class APISupervisor(object):
|
||||
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()
|
||||
|
@ -62,6 +62,20 @@ def api_process_hostcontroll(method):
|
||||
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({
|
||||
|
@ -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'
|
||||
|
@ -99,8 +99,9 @@ class DockerBase(object):
|
||||
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 @@ class DockerBase(object):
|
||||
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)
|
||||
|
@ -74,3 +74,17 @@ class DockerAddon(DockerBase):
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user