diff --git a/API.md b/API.md index f15e7b374..de8aca967 100644 --- a/API.md +++ b/API.md @@ -482,6 +482,7 @@ Get all available addons. "webui": "null|http(s)://[HOST]:port/xy/zx", "gpio": "bool", "devicetree": "bool", + "docker_api": "bool", "audio": "bool", "audio_input": "null|0,0", "audio_output": "null|0,0", diff --git a/hassio/addons/addon.py b/hassio/addons/addon.py index bc0383fab..6dac66d0d 100644 --- a/hassio/addons/addon.py +++ b/hassio/addons/addon.py @@ -25,8 +25,8 @@ from ..const import ( ATTR_HASSIO_API, ATTR_AUDIO, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_IPC, ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES, - ATTR_APPARMOR, ATTR_DEVICETREE, SECURITY_PROFILE, SECURITY_DISABLE, - SECURITY_DEFAULT) + ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, SECURITY_PROFILE, + SECURITY_DISABLE, SECURITY_DEFAULT) from ..coresys import CoreSysAttributes from ..docker.addon import DockerAddon from ..utils.json import write_json_file, read_json_file @@ -335,6 +335,11 @@ class Addon(CoreSysAttributes): """Return if the add-on don't support hass labels.""" return self._mesh.get(ATTR_LEGACY) + @property + def with_docker_api(self): + """Return if the add-on need read-only docker API access.""" + return self._mesh.get(ATTR_DOCKER_API) + @property def access_hassio_api(self): """Return True if the add-on access to hassio api.""" diff --git a/hassio/addons/validate.py b/hassio/addons/validate.py index c77a809c7..008ad3fb1 100644 --- a/hassio/addons/validate.py +++ b/hassio/addons/validate.py @@ -18,7 +18,7 @@ from ..const import ( ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API, ATTR_BUILD_FROM, ATTR_SQUASH, ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY, - ATTR_APPARMOR, ATTR_DEVICETREE) + ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API) from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_DEVICE _LOGGER = logging.getLogger(__name__) @@ -63,7 +63,8 @@ PRIVILEGED_ALL = [ "SYS_RAWIO", "IPC_LOCK", "SYS_TIME", - "SYS_NICE" + "SYS_NICE", + "SYS_RESOURCE" ] BASE_IMAGE = { @@ -116,6 +117,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({ vol.Optional(ATTR_HOMEASSISTANT_API, default=False): vol.Boolean(), vol.Optional(ATTR_STDIN, default=False): vol.Boolean(), vol.Optional(ATTR_LEGACY, default=False): vol.Boolean(), + vol.Optional(ATTR_DOCKER_API, default=False): vol.Boolean(), vol.Optional(ATTR_SERVICES): [vol.Match(RE_SERVICE)], vol.Optional(ATTR_DISCOVERY): [vol.Match(RE_DISCOVERY)], vol.Required(ATTR_OPTIONS): dict, diff --git a/hassio/api/addons.py b/hassio/api/addons.py index badfb0489..12fcc732b 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -17,7 +17,7 @@ from ..const import ( ATTR_CHANGELOG, ATTR_HOST_IPC, ATTR_HOST_DBUS, ATTR_LONG_DESCRIPTION, ATTR_CPU_PERCENT, ATTR_MEMORY_LIMIT, ATTR_MEMORY_USAGE, ATTR_NETWORK_TX, ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES, - ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, + ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT) from ..coresys import CoreSysAttributes from ..validate import DOCKER_PORTS, ALSA_DEVICE @@ -137,6 +137,7 @@ class APIAddons(CoreSysAttributes): ATTR_HOMEASSISTANT_API: addon.access_homeassistant_api, ATTR_GPIO: addon.with_gpio, ATTR_DEVICETREE: addon.with_devicetree, + ATTR_DOCKER_API: addon.with_docker_api, ATTR_AUDIO: addon.with_audio, ATTR_AUDIO_INPUT: addon.audio_input, ATTR_AUDIO_OUTPUT: addon.audio_output, diff --git a/hassio/api/proxy.py b/hassio/api/proxy.py index 24c4b72aa..e42f67fee 100644 --- a/hassio/api/proxy.py +++ b/hassio/api/proxy.py @@ -59,6 +59,8 @@ class APIProxy(CoreSysAttributes): except HomeAssistantAuthError: _LOGGER.error("Authenticate error on API for request %s", path) + except HomeAssistantAPIError: + _LOGGER.error("Error on API for request %s", path) except aiohttp.ClientError as err: _LOGGER.error("Client error on API %s request %s", path, err) except asyncio.TimeoutError: @@ -148,11 +150,12 @@ class APIProxy(CoreSysAttributes): self.sys_homeassistant.access_token = None return await self._websocket_client() - _LOGGER.error( - "Failed authentication to Home-Assistant websocket: %s", data) + raise HomeAssistantAuthError() - except (RuntimeError, HomeAssistantAPIError) as err: + except (RuntimeError, ValueError) as err: _LOGGER.error("Client error on websocket API %s.", err) + except HomeAssistantAuthError as err: + _LOGGER.error("Failed authentication to HomeAssistant websocket") raise HTTPBadGateway() diff --git a/hassio/const.py b/hassio/const.py index 75ca19d13..dc1969c07 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -2,7 +2,7 @@ from pathlib import Path from ipaddress import ip_network -HASSIO_VERSION = '123' +HASSIO_VERSION = '124' URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons" URL_HASSIO_VERSION = \ @@ -178,6 +178,7 @@ ATTR_HASSOS_CLI = 'hassos_cli' ATTR_VERSION_CLI = 'version_cli' ATTR_VERSION_CLI_LATEST = 'version_cli_latest' ATTR_REFRESH_TOKEN = 'refresh_token' +ATTR_DOCKER_API = 'docker_api' SERVICE_MQTT = 'mqtt' diff --git a/hassio/docker/addon.py b/hassio/docker/addon.py index ad38d83b6..1b858dbbe 100644 --- a/hassio/docker/addon.py +++ b/hassio/docker/addon.py @@ -1,6 +1,7 @@ """Init file for HassIO addon docker object.""" import logging import os +from pathlib import Path import docker import requests @@ -204,14 +205,14 @@ class DockerAddon(DockerInterface): # GPIO support if self.addon.with_gpio: - volumes.update({ - "/sys/class/gpio": { - 'bind': "/sys/class/gpio", 'mode': 'rw' - }, - "/sys/devices/platform/soc": { - 'bind': "/sys/devices/platform/soc", 'mode': 'rw' - }, - }) + for gpio_path in ("/sys/class/gpio", "/sys/devices/platform/soc"): + if not Path(gpio_path).exists(): + continue + volumes.update({ + gpio_path: { + 'bind': gpio_path, 'mode': 'rw' + }, + }) # DeviceTree support if self.addon.with_devicetree: @@ -221,6 +222,14 @@ class DockerAddon(DockerInterface): }, }) + # Docker API support + if self.addon.with_docker_api: + volumes.update({ + "/var/run/docker.sock": { + 'bind': "/var/run/docker.sock", 'mode': 'ro' + }, + }) + # Host dbus system if self.addon.host_dbus: volumes.update({ diff --git a/hassio/exceptions.py b/hassio/exceptions.py index cd9499ba3..9cd18e146 100644 --- a/hassio/exceptions.py +++ b/hassio/exceptions.py @@ -1,7 +1,4 @@ """Core Exceptions.""" -import asyncio - -import aiohttp class HassioError(Exception): @@ -26,14 +23,13 @@ class HomeAssistantUpdateError(HomeAssistantError): pass -class HomeAssistantAuthError(HomeAssistantError): - """Home Assistant Auth API exception.""" +class HomeAssistantAPIError(HomeAssistantError): + """Home Assistant API exception.""" pass -class HomeAssistantAPIError( - HomeAssistantAuthError, asyncio.TimeoutError, aiohttp.ClientError): - """Home Assistant API exception.""" +class HomeAssistantAuthError(HomeAssistantAPIError): + """Home Assistant Auth API exception.""" pass diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 3eb3bde4f..832a2d04b 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -355,7 +355,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): return with suppress(asyncio.TimeoutError, aiohttp.ClientError): - async with self.sys_websession_ssl.get( + async with self.sys_websession_ssl.post( f"{self.api_url}/auth/token", timeout=30, data={ @@ -363,15 +363,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): "refresh_token": self.refresh_token } ) as resp: - if resp.status != 200: - _LOGGER.error("Authenticate problem with HomeAssistant!") - raise HomeAssistantAuthError() - tokens = await resp.json() - self.access_token = tokens['access_token'] - return + if resp.status == 200: + _LOGGER.info("Updated HomeAssistant API token") + tokens = await resp.json() + self.access_token = tokens['access_token'] + return _LOGGER.error("Can't update HomeAssistant access token!") - raise HomeAssistantAPIError() + raise HomeAssistantAuthError() @asynccontextmanager async def make_request(self, method, path, json=None, content_type=None, @@ -394,15 +393,20 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): await self.ensure_access_token() headers[hdrs.AUTHORIZATION] = f'Bearer {self.access_token}' - async with getattr(self.sys_websession_ssl, method)( - url, data=data, timeout=timeout, json=json, headers=headers - ) as resp: - # Access token expired - if resp.status == 401 and self.refresh_token: - self.access_token = None - continue - yield resp - return + try: + async with getattr(self.sys_websession_ssl, method)( + url, data=data, timeout=timeout, json=json, + headers=headers + ) as resp: + # Access token expired + if resp.status == 401 and self.refresh_token: + self.access_token = None + continue + yield resp + return + except (asyncio.TimeoutError, aiohttp.ClientError) as err: + _LOGGER.error("Error on call %s: %s", url, err) + break raise HomeAssistantAPIError() diff --git a/requirements.txt b/requirements.txt index f1df28229..31e80730b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ attr==0.3.1 async_timeout==3.0.0 aiohttp==3.3.2 -docker==3.4.0 +docker==3.4.1 colorlog==3.1.2 -voluptuous==0.11.1 +voluptuous==0.11.5 gitpython==2.1.10 pytz==2018.4 pyudev==0.21.0 diff --git a/tox.ini b/tox.ini index e2aff7381..23310edf9 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = lint [testenv] deps = flake8==3.5.0 - pylint==2.0.0 + pylint==2.1.1 -r{toxinidir}/requirements.txt [testenv:lint]