Skip to content

Commit

Permalink
Extend API to view logs from docker (#11)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
pvizeli authored Apr 24, 2017
1 parent 98f0ff2 commit f99c633
Show file tree
Hide file tree
Showing 11 changed files with 108 additions and 16 deletions.
12 changes: 12 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ Optional:

Reload addons/version.

- `/supervisor/logs`

Output the raw docker log

### Host

- `/host/shutdown`
Expand Down Expand Up @@ -123,6 +127,10 @@ Optional:
}
```

- `/homeassistant/logs`

Output the raw docker log

### REST API addons

- `/addons/{addon}/info`
Expand Down Expand Up @@ -163,6 +171,10 @@ Optional:
}
```

- `/addons/{addon}/logs`

Output the raw docker log

## Host Controll

Communicate over unix socket with a host daemon.
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 9 additions & 4 deletions hassio/addons/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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
Expand All @@ -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()
3 changes: 3 additions & 0 deletions hassio/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,15 @@ 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."""
api_hass = APIHomeAssistant(self.config, self.loop, 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."""
Expand All @@ -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."""
Expand Down
8 changes: 7 additions & 1 deletion hassio/api/addons.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
22 changes: 15 additions & 7 deletions hassio/api/homeassistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand All @@ -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,
}

Expand All @@ -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()
10 changes: 9 additions & 1 deletion hassio/api/supervisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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()
14 changes: 14 additions & 0 deletions hassio/api/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
2 changes: 1 addition & 1 deletion hassio/const.py
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
25 changes: 24 additions & 1 deletion hassio/dock/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
14 changes: 14 additions & 0 deletions hassio/dock/addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit f99c633

Please sign in to comment.