Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Hass.io discovery feature for Add-ons #17035

Merged
merged 57 commits into from
Oct 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
3a58aee
Update handler.py
pvizeli Sep 27, 2018
1f27426
Update __init__.py
pvizeli Sep 27, 2018
977f436
Update handler.py
pvizeli Sep 27, 2018
ba9c88d
Update __init__.py
pvizeli Sep 27, 2018
2fe4c42
Create discovery.py
pvizeli Sep 27, 2018
ac9ebea
Update handler.py
pvizeli Sep 27, 2018
c9710cc
Update discovery.py
pvizeli Sep 27, 2018
1fa8eef
Update __init__.py
pvizeli Sep 27, 2018
f05ec65
Update discovery.py
pvizeli Sep 27, 2018
ec18d14
Update discovery.py
pvizeli Sep 27, 2018
8a364ab
Update discovery.py
pvizeli Sep 27, 2018
84b446b
Update struct
pvizeli Sep 30, 2018
ba9af82
Update handler.py
pvizeli Oct 1, 2018
7055bbb
Update discovery.py
pvizeli Oct 1, 2018
bbef1ae
Update discovery.py
pvizeli Oct 1, 2018
b142b87
Update discovery.py
pvizeli Oct 1, 2018
f46ae5b
Update __init__.py
pvizeli Oct 1, 2018
bcffd78
Update discovery.py
pvizeli Oct 1, 2018
406096c
Update discovery.py
pvizeli Oct 1, 2018
47241e5
Update discovery.py
pvizeli Oct 1, 2018
3984d63
Update discovery.py
pvizeli Oct 1, 2018
78f0442
Update discovery.py
pvizeli Oct 1, 2018
544dc6a
Update discovery.py
pvizeli Oct 1, 2018
0b80e73
Update discovery.py
pvizeli Oct 1, 2018
5b14887
Update discovery.py
pvizeli Oct 1, 2018
d75aa8a
Update __init__.py
pvizeli Oct 1, 2018
ed77974
Merge branch 'dev' into hassio-discovery
pvizeli Oct 1, 2018
4a321d4
Update discovery.py
pvizeli Oct 1, 2018
559203f
fix lint
pvizeli Oct 1, 2018
238e34f
Update discovery.py
pvizeli Oct 1, 2018
9f478f4
cleanup old discovery
pvizeli Oct 1, 2018
73b3321
Update discovery.py
pvizeli Oct 1, 2018
e862dc8
Update discovery.py
pvizeli Oct 1, 2018
8668fe6
Merge remote-tracking branch 'origin/dev' into hassio-discovery
pvizeli Oct 1, 2018
93af834
Fix lint
pvizeli Oct 1, 2018
aa81089
Fix tests
pvizeli Oct 1, 2018
77d3e46
Write more tests with new functions
pvizeli Oct 1, 2018
d3d809c
Update test_handler.py
pvizeli Oct 2, 2018
4700718
Create test_discovery.py
pvizeli Oct 2, 2018
1337e10
Update conftest.py
pvizeli Oct 2, 2018
a8547d7
Update test_discovery.py
pvizeli Oct 2, 2018
803ec0f
Update conftest.py
pvizeli Oct 2, 2018
bb13c99
Update test_discovery.py
pvizeli Oct 2, 2018
9f45a95
Update conftest.py
pvizeli Oct 2, 2018
e0db534
Update test_discovery.py
pvizeli Oct 2, 2018
42f1660
Update test_discovery.py
pvizeli Oct 2, 2018
420944f
Update test_discovery.py
pvizeli Oct 2, 2018
bfb92aa
Update test_discovery.py
pvizeli Oct 2, 2018
c1c909e
Update test_discovery.py
pvizeli Oct 2, 2018
b491331
Fix test
pvizeli Oct 2, 2018
0078f9b
Add test
pvizeli Oct 2, 2018
c7e05e0
fix lint
pvizeli Oct 3, 2018
9e30341
Update handler.py
pvizeli Oct 3, 2018
31e07a3
Update discovery.py
balloob Oct 3, 2018
5fdd8fc
Update test_discovery.py
balloob Oct 3, 2018
2708908
fix lint
pvizeli Oct 3, 2018
2a4a55e
Lint
balloob Oct 3, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 27 additions & 23 deletions homeassistant/components/hassio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
from homeassistant.loader import bind_hass
from homeassistant.util.dt import utcnow

from .handler import HassIO
from .handler import HassIO, HassioAPIError
from .discovery import async_setup_discovery
from .http import HassIOView

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -136,29 +137,27 @@ def is_hassio(hass):
async def async_check_config(hass):
"""Check configuration over Hass.io API."""
hassio = hass.data[DOMAIN]
result = await hassio.check_homeassistant_config()

if not result:
return "Hass.io config check API error"
try:
result = await hassio.check_homeassistant_config()
except HassioAPIError as err:
_LOGGER.error("Error on Hass.io API: %s", err)

if result['result'] == "error":
return result['message']
return None


async def async_setup(hass, config):
"""Set up the Hass.io component."""
try:
host = os.environ['HASSIO']
except KeyError:
_LOGGER.error("Missing HASSIO environment variable.")
return False

try:
os.environ['HASSIO_TOKEN']
except KeyError:
_LOGGER.error("Missing HASSIO_TOKEN environment variable.")
# Check local setup
for env in ('HASSIO', 'HASSIO_TOKEN'):
if os.environ.get(env):
continue
_LOGGER.error("Missing %s environment variable.", env)
return False

host = os.environ['HASSIO']
websession = hass.helpers.aiohttp_client.async_get_clientsession()
hass.data[DOMAIN] = hassio = HassIO(hass.loop, websession, host)

Expand Down Expand Up @@ -229,23 +228,25 @@ async def async_service_handler(service):
payload = data

# Call API
ret = await hassio.send_command(
api_command.format(addon=addon, snapshot=snapshot),
payload=payload, timeout=MAP_SERVICE_API[service.service][2]
)

if not ret or ret['result'] != "ok":
_LOGGER.error("Error on Hass.io API: %s", ret['message'])
try:
await hassio.send_command(
api_command.format(addon=addon, snapshot=snapshot),
payload=payload, timeout=MAP_SERVICE_API[service.service][2]
)
except HassioAPIError as err:
_LOGGER.error("Error on Hass.io API: %s", err)

for service, settings in MAP_SERVICE_API.items():
hass.services.async_register(
DOMAIN, service, async_service_handler, schema=settings[1])

async def update_homeassistant_version(now):
"""Update last available Home Assistant version."""
data = await hassio.get_homeassistant_info()
if data:
try:
data = await hassio.get_homeassistant_info()
hass.data[DATA_HOMEASSISTANT_VERSION] = data['last_version']
except HassioAPIError as err:
_LOGGER.warning("Can't read last version: %s", err)

hass.helpers.event.async_track_point_in_utc_time(
update_homeassistant_version, utcnow() + HASSIO_UPDATE_INTERVAL)
Expand Down Expand Up @@ -276,4 +277,7 @@ async def async_handle_core_service(call):
hass.services.async_register(
HASS_DOMAIN, service, async_handle_core_service)

# Init discovery Hass.io feature
async_setup_discovery(hass, hassio, config)

return True
115 changes: 115 additions & 0 deletions homeassistant/components/hassio/discovery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Implement the serivces discovery feature from Hass.io for Add-ons."""
import asyncio
import logging

from aiohttp import web
from aiohttp.web_exceptions import HTTPServiceUnavailable

from homeassistant.core import callback
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.components.http import HomeAssistantView

from .handler import HassioAPIError

_LOGGER = logging.getLogger(__name__)

ATTR_DISCOVERY = 'discovery'
ATTR_ADDON = 'addon'
ATTR_NAME = 'name'
ATTR_SERVICE = 'service'
ATTR_CONFIG = 'config'
ATTR_UUID = 'uuid'


@callback
def async_setup_discovery(hass, hassio, config):
"""Discovery setup."""
hassio_discovery = HassIODiscovery(hass, hassio, config)

# Handle exists discovery messages
async def async_discovery_start_handler(event):
"""Process all exists discovery on startup."""
try:
data = await hassio.retrieve_discovery_messages()
except HassioAPIError as err:
_LOGGER.error("Can't read discover info: %s", err)
return

jobs = [hassio_discovery.async_process_new(discovery)
for discovery in data[ATTR_DISCOVERY]]
if jobs:
await asyncio.wait(jobs)

hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_discovery_start_handler)

hass.http.register_view(hassio_discovery)


class HassIODiscovery(HomeAssistantView):
"""Hass.io view to handle base part."""

name = "api:hassio_push:discovery"
url = "/api/hassio_push/discovery/{uuid}"

def __init__(self, hass, hassio, config):
"""Initialize WebView."""
self.hass = hass
self.hassio = hassio
self.config = config

async def post(self, request, uuid):
"""Handle new discovery requests."""
# Fetch discovery data and prevent injections
try:
data = await self.hassio.get_discovery_message(uuid)
except HassioAPIError as err:
_LOGGER.error("Can't read discovey data: %s", err)
raise HTTPServiceUnavailable() from None

await self.async_process_new(data)
return web.Response()

async def delete(self, request, uuid):
"""Handle remove discovery requests."""
data = request.json()

await self.async_process_del(data)
return web.Response()

async def async_process_new(self, data):
"""Process add discovery entry."""
service = data[ATTR_SERVICE]
config_data = data[ATTR_CONFIG]

# Read addinional Add-on info
try:
addon_info = await self.hassio.get_addon_info(data[ATTR_ADDON])
except HassioAPIError as err:
_LOGGER.error("Can't read add-on info: %s", err)
return
config_data[ATTR_ADDON] = addon_info[ATTR_NAME]

# Use config flow
await self.hass.config_entries.flow.async_init(
service, context={'source': 'hassio'}, data=config_data)

async def async_process_del(self, data):
"""Process remove discovery entry."""
service = data[ATTR_SERVICE]
uuid = data[ATTR_UUID]

# Check if realy deletet / prevent injections
try:
data = await self.hassio.get_discovery_message(uuid)
except HassioAPIError:
pass
else:
_LOGGER.warning("Retrieve wrong unload for %s", service)
return

# Use config flow
for entry in self.hass.config_entries.async_entries(service):
if entry.source != 'hassio':
continue
await self.hass.config_entries.async_remove(entry)
44 changes: 38 additions & 6 deletions homeassistant/components/hassio/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@
X_HASSIO = 'X-HASSIO-KEY'


class HassioAPIError(RuntimeError):
"""Return if a API trow a error."""


def _api_bool(funct):
"""Return a boolean."""
async def _wrapper(*argv, **kwargs):
"""Wrap function."""
data = await funct(*argv, **kwargs)
return data and data['result'] == "ok"
try:
data = await funct(*argv, **kwargs)
return data['result'] == "ok"
except HassioAPIError:
return False

return _wrapper

Expand All @@ -36,9 +43,9 @@ def _api_data(funct):
async def _wrapper(*argv, **kwargs):
"""Wrap function."""
data = await funct(*argv, **kwargs)
if data and data['result'] == "ok":
if data['result'] == "ok":
return data['data']
return None
raise HassioAPIError(data['message'])

return _wrapper

Expand Down Expand Up @@ -68,6 +75,15 @@ def get_homeassistant_info(self):
"""
return self.send_command("/homeassistant/info", method="get")

@_api_data
def get_addon_info(self, addon):
"""Return data for a Add-on.

This method return a coroutine.
"""
return self.send_command(
"/addons/{}/info".format(addon), method="get")

@_api_bool
def restart_homeassistant(self):
"""Restart Home-Assistant container.
Expand All @@ -91,6 +107,22 @@ def check_homeassistant_config(self):
"""
return self.send_command("/homeassistant/check", timeout=300)

@_api_data
def retrieve_discovery_messages(self):
"""Return all discovery data from Hass.io API.

This method return a coroutine.
"""
return self.send_command("/discovery", method="get")

@_api_data
def get_discovery_message(self, uuid):
"""Return a single discovery data message.

This method return a coroutine.
"""
return self.send_command("/discovery/{}".format(uuid), method="get")

@_api_bool
async def update_hass_api(self, http_config, refresh_token):
"""Update Home Assistant API data on Hass.io."""
Expand Down Expand Up @@ -137,7 +169,7 @@ async def send_command(self, command, method="post", payload=None,
if request.status not in (200, 400):
_LOGGER.error(
"%s return code %d.", command, request.status)
return None
raise HassioAPIError()

answer = await request.json()
return answer
Expand All @@ -148,4 +180,4 @@ async def send_command(self, command, method="post", payload=None,
except aiohttp.ClientError as err:
_LOGGER.error("Client error on %s request %s", command, err)

return None
raise HassioAPIError()
6 changes: 3 additions & 3 deletions tests/components/hassio/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import pytest

from homeassistant.setup import async_setup_component
from homeassistant.components.hassio.handler import HassIO
from homeassistant.components.hassio.handler import HassIO, HassioAPIError

from tests.common import mock_coro
from . import API_PASSWORD, HASSIO_TOKEN
Expand All @@ -21,7 +21,7 @@ def hassio_env():
patch.dict(os.environ, {'HASSIO_TOKEN': "123456"}), \
patch('homeassistant.components.hassio.HassIO.'
'get_homeassistant_info',
Mock(return_value=mock_coro(None))):
Mock(side_effect=HassioAPIError())):
yield


Expand All @@ -32,7 +32,7 @@ def hassio_client(hassio_env, hass, aiohttp_client):
Mock(return_value=mock_coro({"result": "ok"}))), \
patch('homeassistant.components.hassio.HassIO.'
'get_homeassistant_info',
Mock(return_value=mock_coro(None))):
Mock(side_effect=HassioAPIError())):
hass.loop.run_until_complete(async_setup_component(hass, 'hassio', {
'http': {
'api_password': API_PASSWORD
Expand Down
Loading