mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-28 11:36:32 +00:00
commit
74e03a9a2e
3
.gitignore
vendored
3
.gitignore
vendored
@ -90,3 +90,6 @@ ENV/
|
|||||||
|
|
||||||
# pylint
|
# pylint
|
||||||
.pylint.d/
|
.pylint.d/
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.vscode/
|
||||||
|
44
API.md
44
API.md
@ -428,11 +428,11 @@ Get all available addons.
|
|||||||
"name": "xy bla",
|
"name": "xy bla",
|
||||||
"slug": "xy",
|
"slug": "xy",
|
||||||
"description": "description",
|
"description": "description",
|
||||||
"arch": ["armhf", "aarch64", "i386", "amd64"],
|
|
||||||
"repository": "core|local|REP_ID",
|
"repository": "core|local|REP_ID",
|
||||||
"version": "LAST_VERSION",
|
"version": "LAST_VERSION",
|
||||||
"installed": "none|INSTALL_VERSION",
|
"installed": "none|INSTALL_VERSION",
|
||||||
"detached": "bool",
|
"detached": "bool",
|
||||||
|
"available": "bool",
|
||||||
"build": "bool",
|
"build": "bool",
|
||||||
"url": "null|url",
|
"url": "null|url",
|
||||||
"icon": "bool",
|
"icon": "bool",
|
||||||
@ -463,6 +463,9 @@ Get all available addons.
|
|||||||
"auto_update": "bool",
|
"auto_update": "bool",
|
||||||
"url": "null|url of addon",
|
"url": "null|url of addon",
|
||||||
"detached": "bool",
|
"detached": "bool",
|
||||||
|
"available": "bool",
|
||||||
|
"arch": ["armhf", "aarch64", "i386", "amd64"],
|
||||||
|
"machine": "[raspberrypi2, tinker]",
|
||||||
"repository": "12345678|null",
|
"repository": "12345678|null",
|
||||||
"version": "null|VERSION_INSTALLED",
|
"version": "null|VERSION_INSTALLED",
|
||||||
"last_version": "LAST_VERSION",
|
"last_version": "LAST_VERSION",
|
||||||
@ -496,8 +499,8 @@ Get all available addons.
|
|||||||
"audio": "bool",
|
"audio": "bool",
|
||||||
"audio_input": "null|0,0",
|
"audio_input": "null|0,0",
|
||||||
"audio_output": "null|0,0",
|
"audio_output": "null|0,0",
|
||||||
"services": "null|['mqtt']",
|
"services_role": "['service:access']",
|
||||||
"discovery": "null|['component/platform']"
|
"discovery": "['service']"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -573,12 +576,13 @@ Write data to add-on stdin
|
|||||||
|
|
||||||
### Service discovery
|
### Service discovery
|
||||||
|
|
||||||
- GET `/services/discovery`
|
- GET `/discovery`
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"discovery": [
|
"discovery": [
|
||||||
{
|
{
|
||||||
"provider": "name",
|
"addon": "slug",
|
||||||
|
"service": "name",
|
||||||
"uuid": "uuid",
|
"uuid": "uuid",
|
||||||
"component": "component",
|
"component": "component",
|
||||||
"platform": "null|platform",
|
"platform": "null|platform",
|
||||||
@ -588,10 +592,11 @@ Write data to add-on stdin
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- GET `/services/discovery/{UUID}`
|
- GET `/discovery/{UUID}`
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"provider": "name",
|
"addon": "slug",
|
||||||
|
"service": "name",
|
||||||
"uuid": "uuid",
|
"uuid": "uuid",
|
||||||
"component": "component",
|
"component": "component",
|
||||||
"platform": "null|platform",
|
"platform": "null|platform",
|
||||||
@ -599,9 +604,10 @@ Write data to add-on stdin
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- POST `/services/discovery`
|
- POST `/discovery`
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"service": "name",
|
||||||
"component": "component",
|
"component": "component",
|
||||||
"platform": "null|platform",
|
"platform": "null|platform",
|
||||||
"config": {}
|
"config": {}
|
||||||
@ -615,7 +621,7 @@ return:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- DEL `/services/discovery/{UUID}`
|
- DEL `/discovery/{UUID}`
|
||||||
|
|
||||||
- GET `/services`
|
- GET `/services`
|
||||||
```json
|
```json
|
||||||
@ -624,7 +630,7 @@ return:
|
|||||||
{
|
{
|
||||||
"slug": "name",
|
"slug": "name",
|
||||||
"available": "bool",
|
"available": "bool",
|
||||||
"provider": "null|name|list"
|
"providers": "list"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -632,12 +638,10 @@ return:
|
|||||||
|
|
||||||
#### MQTT
|
#### MQTT
|
||||||
|
|
||||||
This service performs an auto discovery to Home-Assistant.
|
|
||||||
|
|
||||||
- GET `/services/mqtt`
|
- GET `/services/mqtt`
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"provider": "name",
|
"addon": "name",
|
||||||
"host": "xy",
|
"host": "xy",
|
||||||
"port": "8883",
|
"port": "8883",
|
||||||
"ssl": "bool",
|
"ssl": "bool",
|
||||||
@ -660,3 +664,17 @@ This service performs an auto discovery to Home-Assistant.
|
|||||||
```
|
```
|
||||||
|
|
||||||
- DEL `/services/mqtt`
|
- DEL `/services/mqtt`
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
|
||||||
|
- GET `/version`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"supervisor": "version",
|
||||||
|
"homeassistant": "version",
|
||||||
|
"hassos": "null|version",
|
||||||
|
"machine": "type",
|
||||||
|
"arch": "arch",
|
||||||
|
"channel": "stable|beta|dev"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO addons."""
|
"""Init file for Hass.io add-ons."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -14,10 +14,10 @@ BUILTIN_REPOSITORIES = set((REPOSITORY_CORE, REPOSITORY_LOCAL))
|
|||||||
|
|
||||||
|
|
||||||
class AddonManager(CoreSysAttributes):
|
class AddonManager(CoreSysAttributes):
|
||||||
"""Manage addons inside HassIO."""
|
"""Manage add-ons inside Hass.io."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys):
|
||||||
"""Initialize docker base wrapper."""
|
"""Initialize Docker base wrapper."""
|
||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
self.data = AddonsData(coresys)
|
self.data = AddonsData(coresys)
|
||||||
self.addons_obj = {}
|
self.addons_obj = {}
|
||||||
@ -25,51 +25,44 @@ class AddonManager(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def list_addons(self):
|
def list_addons(self):
|
||||||
"""Return a list of all addons."""
|
"""Return a list of all add-ons."""
|
||||||
return list(self.addons_obj.values())
|
return list(self.addons_obj.values())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def list_installed(self):
|
def list_installed(self):
|
||||||
"""Return a list of installed addons."""
|
"""Return a list of installed add-ons."""
|
||||||
return [addon for addon in self.addons_obj.values()
|
return [addon for addon in self.addons_obj.values()
|
||||||
if addon.is_installed]
|
if addon.is_installed]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def list_repositories(self):
|
def list_repositories(self):
|
||||||
"""Return list of addon repositories."""
|
"""Return list of add-on repositories."""
|
||||||
return list(self.repositories_obj.values())
|
return list(self.repositories_obj.values())
|
||||||
|
|
||||||
def get(self, addon_slug):
|
def get(self, addon_slug):
|
||||||
"""Return an add-on from slug."""
|
"""Return an add-on from slug."""
|
||||||
return self.addons_obj.get(addon_slug)
|
return self.addons_obj.get(addon_slug)
|
||||||
|
|
||||||
def from_uuid(self, uuid):
|
|
||||||
"""Return an add-on from uuid."""
|
|
||||||
for addon in self.list_addons:
|
|
||||||
if addon.is_installed and uuid == addon.uuid:
|
|
||||||
return addon
|
|
||||||
return None
|
|
||||||
|
|
||||||
def from_token(self, token):
|
def from_token(self, token):
|
||||||
"""Return an add-on from hassio token."""
|
"""Return an add-on from Hass.io token."""
|
||||||
for addon in self.list_addons:
|
for addon in self.list_addons:
|
||||||
if addon.is_installed and token == addon.hassio_token:
|
if addon.is_installed and token == addon.hassio_token:
|
||||||
return addon
|
return addon
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def load(self):
|
async def load(self):
|
||||||
"""Startup addon management."""
|
"""Start up add-on management."""
|
||||||
self.data.reload()
|
self.data.reload()
|
||||||
|
|
||||||
# init hassio built-in repositories
|
# Init Hass.io built-in repositories
|
||||||
repositories = \
|
repositories = \
|
||||||
set(self.sys_config.addons_repositories) | BUILTIN_REPOSITORIES
|
set(self.sys_config.addons_repositories) | BUILTIN_REPOSITORIES
|
||||||
|
|
||||||
# init custom repositories & load addons
|
# Init custom repositories and load add-ons
|
||||||
await self.load_repositories(repositories)
|
await self.load_repositories(repositories)
|
||||||
|
|
||||||
async def reload(self):
|
async def reload(self):
|
||||||
"""Update addons from repo and reload list."""
|
"""Update add-ons from repository and reload list."""
|
||||||
tasks = [repository.update() for repository in
|
tasks = [repository.update() for repository in
|
||||||
self.repositories_obj.values()]
|
self.repositories_obj.values()]
|
||||||
if tasks:
|
if tasks:
|
||||||
@ -113,14 +106,14 @@ class AddonManager(CoreSysAttributes):
|
|||||||
await self.load_addons()
|
await self.load_addons()
|
||||||
|
|
||||||
async def load_addons(self):
|
async def load_addons(self):
|
||||||
"""Update/add internal addon store."""
|
"""Update/add internal add-on store."""
|
||||||
all_addons = set(self.data.system) | set(self.data.cache)
|
all_addons = set(self.data.system) | set(self.data.cache)
|
||||||
|
|
||||||
# calc diff
|
# calc diff
|
||||||
add_addons = all_addons - set(self.addons_obj)
|
add_addons = all_addons - set(self.addons_obj)
|
||||||
del_addons = set(self.addons_obj) - all_addons
|
del_addons = set(self.addons_obj) - all_addons
|
||||||
|
|
||||||
_LOGGER.info("Load addons: %d all - %d new - %d remove",
|
_LOGGER.info("Load add-ons: %d all - %d new - %d remove",
|
||||||
len(all_addons), len(add_addons), len(del_addons))
|
len(all_addons), len(add_addons), len(del_addons))
|
||||||
|
|
||||||
# new addons
|
# new addons
|
||||||
@ -139,14 +132,14 @@ class AddonManager(CoreSysAttributes):
|
|||||||
self.addons_obj.pop(addon_slug)
|
self.addons_obj.pop(addon_slug)
|
||||||
|
|
||||||
async def boot(self, stage):
|
async def boot(self, stage):
|
||||||
"""Boot addons with mode auto."""
|
"""Boot add-ons with mode auto."""
|
||||||
tasks = []
|
tasks = []
|
||||||
for addon in self.addons_obj.values():
|
for addon in self.addons_obj.values():
|
||||||
if addon.is_installed and addon.boot == BOOT_AUTO and \
|
if addon.is_installed and addon.boot == BOOT_AUTO and \
|
||||||
addon.startup == stage:
|
addon.startup == stage:
|
||||||
tasks.append(addon.start())
|
tasks.append(addon.start())
|
||||||
|
|
||||||
_LOGGER.info("Startup %s run %d addons", stage, len(tasks))
|
_LOGGER.info("Startup %s run %d add-ons", stage, len(tasks))
|
||||||
if tasks:
|
if tasks:
|
||||||
await asyncio.wait(tasks)
|
await asyncio.wait(tasks)
|
||||||
await asyncio.sleep(self.sys_config.wait_boot)
|
await asyncio.sleep(self.sys_config.wait_boot)
|
||||||
@ -160,6 +153,6 @@ class AddonManager(CoreSysAttributes):
|
|||||||
addon.startup == stage:
|
addon.startup == stage:
|
||||||
tasks.append(addon.stop())
|
tasks.append(addon.stop())
|
||||||
|
|
||||||
_LOGGER.info("Shutdown %s stop %d addons", stage, len(tasks))
|
_LOGGER.info("Shutdown %s stop %d add-ons", stage, len(tasks))
|
||||||
if tasks:
|
if tasks:
|
||||||
await asyncio.wait(tasks)
|
await asyncio.wait(tasks)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO addons."""
|
"""Init file for Hass.io add-ons."""
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
import logging
|
import logging
|
||||||
@ -13,7 +13,8 @@ import voluptuous as vol
|
|||||||
from voluptuous.humanize import humanize_error
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
from .validate import (
|
from .validate import (
|
||||||
validate_options, SCHEMA_ADDON_SNAPSHOT, RE_VOLUME, RE_SERVICE)
|
validate_options, SCHEMA_ADDON_SNAPSHOT, RE_VOLUME, RE_SERVICE,
|
||||||
|
MACHINE_ALL)
|
||||||
from .utils import check_installed, remove_data
|
from .utils import check_installed, remove_data
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_BOOT, ATTR_MAP,
|
ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_BOOT, ATTR_MAP,
|
||||||
@ -27,6 +28,7 @@ from ..const import (
|
|||||||
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES,
|
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_DISCOVERY, ATTR_SERVICES,
|
||||||
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS,
|
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS,
|
||||||
ATTR_PROTECTED, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE,
|
ATTR_PROTECTED, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE,
|
||||||
|
ATTR_MACHINE,
|
||||||
SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT)
|
SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..docker.addon import DockerAddon
|
from ..docker.addon import DockerAddon
|
||||||
@ -43,7 +45,7 @@ RE_WEBUI = re.compile(
|
|||||||
|
|
||||||
|
|
||||||
class Addon(CoreSysAttributes):
|
class Addon(CoreSysAttributes):
|
||||||
"""Hold data for addon inside HassIO."""
|
"""Hold data for add-on inside Hass.io."""
|
||||||
|
|
||||||
def __init__(self, coresys, slug):
|
def __init__(self, coresys, slug):
|
||||||
"""Initialize data holder."""
|
"""Initialize data holder."""
|
||||||
@ -59,29 +61,38 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def slug(self):
|
def slug(self):
|
||||||
"""Return slug/id of addon."""
|
"""Return slug/id of add-on."""
|
||||||
return self._id
|
return self._id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _mesh(self):
|
def _mesh(self):
|
||||||
"""Return addon data from system or cache."""
|
"""Return add-on data from system or cache."""
|
||||||
return self._data.system.get(self._id, self._data.cache.get(self._id))
|
return self._data.system.get(self._id, self._data.cache.get(self._id))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _data(self):
|
def _data(self):
|
||||||
"""Return addons data storage."""
|
"""Return add-ons data storage."""
|
||||||
return self.sys_addons.data
|
return self.sys_addons.data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_installed(self):
|
def is_installed(self):
|
||||||
"""Return True if an addon is installed."""
|
"""Return True if an add-on is installed."""
|
||||||
return self._id in self._data.system
|
return self._id in self._data.system
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_detached(self):
|
def is_detached(self):
|
||||||
"""Return True if addon is detached."""
|
"""Return True if add-on is detached."""
|
||||||
return self._id not in self._data.cache
|
return self._id not in self._data.cache
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return True if this add-on is available on this platform."""
|
||||||
|
if self.sys_arch not in self.supported_arch:
|
||||||
|
return False
|
||||||
|
if self.sys_machine not in self.supported_machine:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def version_installed(self):
|
def version_installed(self):
|
||||||
"""Return installed version."""
|
"""Return installed version."""
|
||||||
@ -97,19 +108,19 @@ class Addon(CoreSysAttributes):
|
|||||||
self._data.save_data()
|
self._data.save_data()
|
||||||
|
|
||||||
def _set_uninstall(self):
|
def _set_uninstall(self):
|
||||||
"""Set addon as uninstalled."""
|
"""Set add-on as uninstalled."""
|
||||||
self._data.system.pop(self._id, None)
|
self._data.system.pop(self._id, None)
|
||||||
self._data.user.pop(self._id, None)
|
self._data.user.pop(self._id, None)
|
||||||
self._data.save_data()
|
self._data.save_data()
|
||||||
|
|
||||||
def _set_update(self, version):
|
def _set_update(self, version):
|
||||||
"""Update version of addon."""
|
"""Update version of add-on."""
|
||||||
self._data.system[self._id] = deepcopy(self._data.cache[self._id])
|
self._data.system[self._id] = deepcopy(self._data.cache[self._id])
|
||||||
self._data.user[self._id][ATTR_VERSION] = version
|
self._data.user[self._id][ATTR_VERSION] = version
|
||||||
self._data.save_data()
|
self._data.save_data()
|
||||||
|
|
||||||
def _restore_data(self, user, system):
|
def _restore_data(self, user, system):
|
||||||
"""Restore data to addon."""
|
"""Restore data to add-on."""
|
||||||
self._data.user[self._id] = deepcopy(user)
|
self._data.user[self._id] = deepcopy(user)
|
||||||
self._data.system[self._id] = deepcopy(system)
|
self._data.system[self._id] = deepcopy(system)
|
||||||
self._data.save_data()
|
self._data.save_data()
|
||||||
@ -126,7 +137,7 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@options.setter
|
@options.setter
|
||||||
def options(self, value):
|
def options(self, value):
|
||||||
"""Store user addon options."""
|
"""Store user add-on options."""
|
||||||
if value is None:
|
if value is None:
|
||||||
self._data.user[self._id][ATTR_OPTIONS] = {}
|
self._data.user[self._id][ATTR_OPTIONS] = {}
|
||||||
else:
|
else:
|
||||||
@ -158,7 +169,7 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return name of addon."""
|
"""Return name of add-on."""
|
||||||
return self._mesh[ATTR_NAME]
|
return self._mesh[ATTR_NAME]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -175,14 +186,14 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def hassio_token(self):
|
def hassio_token(self):
|
||||||
"""Return access token for hass.io API."""
|
"""Return access token for Hass.io API."""
|
||||||
if self.is_installed:
|
if self.is_installed:
|
||||||
return self._data.user[self._id].get(ATTR_ACCESS_TOKEN)
|
return self._data.user[self._id].get(ATTR_ACCESS_TOKEN)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
"""Return description of addon."""
|
"""Return description of add-on."""
|
||||||
return self._mesh[ATTR_DESCRIPTON]
|
return self._mesh[ATTR_DESCRIPTON]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -200,56 +211,55 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def repository(self):
|
def repository(self):
|
||||||
"""Return repository of addon."""
|
"""Return repository of add-on."""
|
||||||
return self._mesh[ATTR_REPOSITORY]
|
return self._mesh[ATTR_REPOSITORY]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_version(self):
|
def last_version(self):
|
||||||
"""Return version of addon."""
|
"""Return version of add-on."""
|
||||||
if self._id in self._data.cache:
|
if self._id in self._data.cache:
|
||||||
return self._data.cache[self._id][ATTR_VERSION]
|
return self._data.cache[self._id][ATTR_VERSION]
|
||||||
return self.version_installed
|
return self.version_installed
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def protected(self):
|
def protected(self):
|
||||||
"""Return if addon is in protected mode."""
|
"""Return if add-on is in protected mode."""
|
||||||
if self.is_installed:
|
if self.is_installed:
|
||||||
return self._data.user[self._id][ATTR_PROTECTED]
|
return self._data.user[self._id][ATTR_PROTECTED]
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@protected.setter
|
@protected.setter
|
||||||
def protected(self, value):
|
def protected(self, value):
|
||||||
"""Set addon in protected mode."""
|
"""Set add-on in protected mode."""
|
||||||
self._data.user[self._id][ATTR_PROTECTED] = value
|
self._data.user[self._id][ATTR_PROTECTED] = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def startup(self):
|
def startup(self):
|
||||||
"""Return startup type of addon."""
|
"""Return startup type of add-on."""
|
||||||
return self._mesh.get(ATTR_STARTUP)
|
return self._mesh.get(ATTR_STARTUP)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def services(self):
|
def services_role(self):
|
||||||
"""Return dict of services with rights."""
|
"""Return dict of services with rights."""
|
||||||
raw_services = self._mesh.get(ATTR_SERVICES)
|
raw_services = self._mesh.get(ATTR_SERVICES)
|
||||||
if not raw_services:
|
if not raw_services:
|
||||||
return None
|
return {}
|
||||||
|
|
||||||
formated_services = {}
|
services = {}
|
||||||
for data in raw_services:
|
for data in raw_services:
|
||||||
service = RE_SERVICE.match(data)
|
service = RE_SERVICE.match(data)
|
||||||
formated_services[service.group('service')] = \
|
services[service.group('service')] = service.group('rights')
|
||||||
service.group('rights') or 'ro'
|
|
||||||
|
|
||||||
return formated_services
|
return services
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def discovery(self):
|
def discovery(self):
|
||||||
"""Return list of discoverable components/platforms."""
|
"""Return list of discoverable components/platforms."""
|
||||||
return self._mesh.get(ATTR_DISCOVERY)
|
return self._mesh.get(ATTR_DISCOVERY, [])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ports(self):
|
def ports(self):
|
||||||
"""Return ports of addon."""
|
"""Return ports of add-on."""
|
||||||
if self.host_network or ATTR_PORTS not in self._mesh:
|
if self.host_network or ATTR_PORTS not in self._mesh:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -260,7 +270,7 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@ports.setter
|
@ports.setter
|
||||||
def ports(self, value):
|
def ports(self, value):
|
||||||
"""Set custom ports of addon."""
|
"""Set custom ports of add-on."""
|
||||||
if value is None:
|
if value is None:
|
||||||
self._data.user[self._id].pop(ATTR_NETWORK, None)
|
self._data.user[self._id].pop(ATTR_NETWORK, None)
|
||||||
else:
|
else:
|
||||||
@ -304,42 +314,42 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def host_network(self):
|
def host_network(self):
|
||||||
"""Return True if addon run on host network."""
|
"""Return True if add-on run on host network."""
|
||||||
return self._mesh[ATTR_HOST_NETWORK]
|
return self._mesh[ATTR_HOST_NETWORK]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host_pid(self):
|
def host_pid(self):
|
||||||
"""Return True if addon run on host PID namespace."""
|
"""Return True if add-on run on host PID namespace."""
|
||||||
return self._mesh[ATTR_HOST_PID]
|
return self._mesh[ATTR_HOST_PID]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host_ipc(self):
|
def host_ipc(self):
|
||||||
"""Return True if addon run on host IPC namespace."""
|
"""Return True if add-on run on host IPC namespace."""
|
||||||
return self._mesh[ATTR_HOST_IPC]
|
return self._mesh[ATTR_HOST_IPC]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host_dbus(self):
|
def host_dbus(self):
|
||||||
"""Return True if addon run on host DBUS."""
|
"""Return True if add-on run on host D-BUS."""
|
||||||
return self._mesh[ATTR_HOST_DBUS]
|
return self._mesh[ATTR_HOST_DBUS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def devices(self):
|
def devices(self):
|
||||||
"""Return devices of addon."""
|
"""Return devices of add-on."""
|
||||||
return self._mesh.get(ATTR_DEVICES)
|
return self._mesh.get(ATTR_DEVICES)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auto_uart(self):
|
def auto_uart(self):
|
||||||
"""Return True if we should map all uart device."""
|
"""Return True if we should map all UART device."""
|
||||||
return self._mesh.get(ATTR_AUTO_UART)
|
return self._mesh.get(ATTR_AUTO_UART)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tmpfs(self):
|
def tmpfs(self):
|
||||||
"""Return tmpfs of addon."""
|
"""Return tmpfs of add-on."""
|
||||||
return self._mesh.get(ATTR_TMPFS)
|
return self._mesh.get(ATTR_TMPFS)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def environment(self):
|
def environment(self):
|
||||||
"""Return environment of addon."""
|
"""Return environment of add-on."""
|
||||||
return self._mesh.get(ATTR_ENVIRONMENT)
|
return self._mesh.get(ATTR_ENVIRONMENT)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -349,7 +359,7 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def apparmor(self):
|
def apparmor(self):
|
||||||
"""Return True if apparmor is enabled."""
|
"""Return True if AppArmor is enabled."""
|
||||||
if not self._mesh.get(ATTR_APPARMOR):
|
if not self._mesh.get(ATTR_APPARMOR):
|
||||||
return SECURITY_DISABLE
|
return SECURITY_DISABLE
|
||||||
elif self.sys_host.apparmor.exists(self.slug):
|
elif self.sys_host.apparmor.exists(self.slug):
|
||||||
@ -358,22 +368,22 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def legacy(self):
|
def legacy(self):
|
||||||
"""Return if the add-on don't support hass labels."""
|
"""Return if the add-on don't support Home Assistant labels."""
|
||||||
return self._mesh.get(ATTR_LEGACY)
|
return self._mesh.get(ATTR_LEGACY)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def access_docker_api(self):
|
def access_docker_api(self):
|
||||||
"""Return if the add-on need read-only docker API access."""
|
"""Return if the add-on need read-only Docker API access."""
|
||||||
return self._mesh.get(ATTR_DOCKER_API)
|
return self._mesh.get(ATTR_DOCKER_API)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def access_hassio_api(self):
|
def access_hassio_api(self):
|
||||||
"""Return True if the add-on access to hassio api."""
|
"""Return True if the add-on access to Hass.io REASTful API."""
|
||||||
return self._mesh[ATTR_HASSIO_API]
|
return self._mesh[ATTR_HASSIO_API]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def access_homeassistant_api(self):
|
def access_homeassistant_api(self):
|
||||||
"""Return True if the add-on access to Home-Assistant api proxy."""
|
"""Return True if the add-on access to Home Assistant API proxy."""
|
||||||
return self._mesh[ATTR_HOMEASSISTANT_API]
|
return self._mesh[ATTR_HOMEASSISTANT_API]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -388,7 +398,7 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def with_gpio(self):
|
def with_gpio(self):
|
||||||
"""Return True if the add-on access to gpio interface."""
|
"""Return True if the add-on access to GPIO interface."""
|
||||||
return self._mesh[ATTR_GPIO]
|
return self._mesh[ATTR_GPIO]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -445,7 +455,7 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
"""Return url of addon."""
|
"""Return URL of add-on."""
|
||||||
return self._mesh.get(ATTR_URL)
|
return self._mesh.get(ATTR_URL)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -468,12 +478,17 @@ class Addon(CoreSysAttributes):
|
|||||||
"""Return list of supported arch."""
|
"""Return list of supported arch."""
|
||||||
return self._mesh[ATTR_ARCH]
|
return self._mesh[ATTR_ARCH]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_machine(self):
|
||||||
|
"""Return list of supported machine."""
|
||||||
|
return self._mesh.get(ATTR_MACHINE) or MACHINE_ALL
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image(self):
|
def image(self):
|
||||||
"""Return image name of addon."""
|
"""Return image name of add-on."""
|
||||||
addon_data = self._mesh
|
addon_data = self._mesh
|
||||||
|
|
||||||
# Repository with dockerhub images
|
# Repository with Dockerhub images
|
||||||
if ATTR_IMAGE in addon_data:
|
if ATTR_IMAGE in addon_data:
|
||||||
return addon_data[ATTR_IMAGE].format(arch=self.sys_arch)
|
return addon_data[ATTR_IMAGE].format(arch=self.sys_arch)
|
||||||
|
|
||||||
@ -484,12 +499,12 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def need_build(self):
|
def need_build(self):
|
||||||
"""Return True if this addon need a local build."""
|
"""Return True if this add-on need a local build."""
|
||||||
return ATTR_IMAGE not in self._mesh
|
return ATTR_IMAGE not in self._mesh
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def map_volumes(self):
|
def map_volumes(self):
|
||||||
"""Return a dict of {volume: policy} from addon."""
|
"""Return a dict of {volume: policy} from add-on."""
|
||||||
volumes = {}
|
volumes = {}
|
||||||
for volume in self._mesh[ATTR_MAP]:
|
for volume in self._mesh[ATTR_MAP]:
|
||||||
result = RE_VOLUME.match(volume)
|
result = RE_VOLUME.match(volume)
|
||||||
@ -499,37 +514,37 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def path_data(self):
|
def path_data(self):
|
||||||
"""Return addon data path inside supervisor."""
|
"""Return add-on data path inside Supervisor."""
|
||||||
return Path(self.sys_config.path_addons_data, self._id)
|
return Path(self.sys_config.path_addons_data, self._id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_data(self):
|
def path_extern_data(self):
|
||||||
"""Return addon data path external for docker."""
|
"""Return add-on data path external for Docker."""
|
||||||
return PurePath(self.sys_config.path_extern_addons_data, self._id)
|
return PurePath(self.sys_config.path_extern_addons_data, self._id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_options(self):
|
def path_options(self):
|
||||||
"""Return path to addons options."""
|
"""Return path to add-on options."""
|
||||||
return Path(self.path_data, "options.json")
|
return Path(self.path_data, "options.json")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_location(self):
|
def path_location(self):
|
||||||
"""Return path to this addon."""
|
"""Return path to this add-on."""
|
||||||
return Path(self._mesh[ATTR_LOCATON])
|
return Path(self._mesh[ATTR_LOCATON])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_icon(self):
|
def path_icon(self):
|
||||||
"""Return path to addon icon."""
|
"""Return path to add-on icon."""
|
||||||
return Path(self.path_location, 'icon.png')
|
return Path(self.path_location, 'icon.png')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_logo(self):
|
def path_logo(self):
|
||||||
"""Return path to addon logo."""
|
"""Return path to add-on logo."""
|
||||||
return Path(self.path_location, 'logo.png')
|
return Path(self.path_location, 'logo.png')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_changelog(self):
|
def path_changelog(self):
|
||||||
"""Return path to addon changelog."""
|
"""Return path to add-on changelog."""
|
||||||
return Path(self.path_location, 'CHANGELOG.md')
|
return Path(self.path_location, 'CHANGELOG.md')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -544,15 +559,15 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def path_extern_asound(self):
|
def path_extern_asound(self):
|
||||||
"""Return path to asound config for docker."""
|
"""Return path to asound config for Docker."""
|
||||||
return Path(self.sys_config.path_extern_tmp, f"{self.slug}_asound")
|
return Path(self.sys_config.path_extern_tmp, f"{self.slug}_asound")
|
||||||
|
|
||||||
def save_data(self):
|
def save_data(self):
|
||||||
"""Save data of addon."""
|
"""Save data of add-on."""
|
||||||
self.sys_addons.data.save_data()
|
self.sys_addons.data.save_data()
|
||||||
|
|
||||||
def write_options(self):
|
def write_options(self):
|
||||||
"""Return True if addon options is written to data."""
|
"""Return True if add-on options is written to data."""
|
||||||
schema = self.schema
|
schema = self.schema
|
||||||
options = self.options
|
options = self.options
|
||||||
|
|
||||||
@ -560,15 +575,22 @@ class Addon(CoreSysAttributes):
|
|||||||
schema(options)
|
schema(options)
|
||||||
write_json_file(self.path_options, options)
|
write_json_file(self.path_options, options)
|
||||||
except vol.Invalid as ex:
|
except vol.Invalid as ex:
|
||||||
_LOGGER.error("Addon %s have wrong options: %s", self._id,
|
_LOGGER.error("Add-on %s have wrong options: %s", self._id,
|
||||||
humanize_error(options, ex))
|
humanize_error(options, ex))
|
||||||
except (OSError, json.JSONDecodeError) as err:
|
except (OSError, json.JSONDecodeError) as err:
|
||||||
_LOGGER.error("Addon %s can't write options: %s", self._id, err)
|
_LOGGER.error("Add-on %s can't write options: %s", self._id, err)
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def remove_discovery(self):
|
||||||
|
"""Remove all discovery message from add-on."""
|
||||||
|
for message in self.sys_discovery.list_messages:
|
||||||
|
if message.addon != self.slug:
|
||||||
|
continue
|
||||||
|
self.sys_discovery.remove(message)
|
||||||
|
|
||||||
def write_asound(self):
|
def write_asound(self):
|
||||||
"""Write asound config to file and return True on success."""
|
"""Write asound config to file and return True on success."""
|
||||||
asound_config = self.sys_host.alsa.asound(
|
asound_config = self.sys_host.alsa.asound(
|
||||||
@ -578,7 +600,7 @@ class Addon(CoreSysAttributes):
|
|||||||
with self.path_asound.open('w') as config_file:
|
with self.path_asound.open('w') as config_file:
|
||||||
config_file.write(asound_config)
|
config_file.write(asound_config)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
_LOGGER.error("Addon %s can't write asound: %s", self._id, err)
|
_LOGGER.error("Add-on %s can't write asound: %s", self._id, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@ -606,7 +628,7 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def schema(self):
|
def schema(self):
|
||||||
"""Create a schema for addon options."""
|
"""Create a schema for add-on options."""
|
||||||
raw_schema = self._mesh[ATTR_SCHEMA]
|
raw_schema = self._mesh[ATTR_SCHEMA]
|
||||||
|
|
||||||
if isinstance(raw_schema, bool):
|
if isinstance(raw_schema, bool):
|
||||||
@ -614,7 +636,7 @@ class Addon(CoreSysAttributes):
|
|||||||
return vol.Schema(vol.All(dict, validate_options(raw_schema)))
|
return vol.Schema(vol.All(dict, validate_options(raw_schema)))
|
||||||
|
|
||||||
def test_update_schema(self):
|
def test_update_schema(self):
|
||||||
"""Check if the exists config valid after update."""
|
"""Check if the existing configuration is valid after update."""
|
||||||
if not self.is_installed or self.is_detached:
|
if not self.is_installed or self.is_detached:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -644,19 +666,19 @@ class Addon(CoreSysAttributes):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
async def install(self):
|
async def install(self):
|
||||||
"""Install an addon."""
|
"""Install an add-on."""
|
||||||
if self.sys_arch not in self.supported_arch:
|
if not self.available:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Addon %s not supported on %s", self._id, self.sys_arch)
|
"Add-on %s not supported on %s", self._id, self.sys_arch)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.is_installed:
|
if self.is_installed:
|
||||||
_LOGGER.error("Addon %s is already installed", self._id)
|
_LOGGER.error("Add-on %s is already installed", self._id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.path_data.is_dir():
|
if not self.path_data.is_dir():
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Create Home-Assistant addon data folder %s", self.path_data)
|
"Create Home Assistant add-on data folder %s", self.path_data)
|
||||||
self.path_data.mkdir()
|
self.path_data.mkdir()
|
||||||
|
|
||||||
# Setup/Fix AppArmor profile
|
# Setup/Fix AppArmor profile
|
||||||
@ -670,13 +692,13 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def uninstall(self):
|
async def uninstall(self):
|
||||||
"""Remove an addon."""
|
"""Remove an add-on."""
|
||||||
if not await self.instance.remove():
|
if not await self.instance.remove():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.path_data.is_dir():
|
if self.path_data.is_dir():
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Remove Home-Assistant addon data folder %s", self.path_data)
|
"Remove Home Assistant add-on data folder %s", self.path_data)
|
||||||
await remove_data(self.path_data)
|
await remove_data(self.path_data)
|
||||||
|
|
||||||
# Cleanup audio settings
|
# Cleanup audio settings
|
||||||
@ -684,16 +706,19 @@ class Addon(CoreSysAttributes):
|
|||||||
with suppress(OSError):
|
with suppress(OSError):
|
||||||
self.path_asound.unlink()
|
self.path_asound.unlink()
|
||||||
|
|
||||||
# Cleanup apparmor profile
|
# Cleanup AppArmor profile
|
||||||
if self.sys_host.apparmor.exists(self.slug):
|
if self.sys_host.apparmor.exists(self.slug):
|
||||||
with suppress(HostAppArmorError):
|
with suppress(HostAppArmorError):
|
||||||
await self.sys_host.apparmor.remove_profile(self.slug)
|
await self.sys_host.apparmor.remove_profile(self.slug)
|
||||||
|
|
||||||
|
# Remove discovery messages
|
||||||
|
self.remove_discovery()
|
||||||
|
|
||||||
self._set_uninstall()
|
self._set_uninstall()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def state(self):
|
async def state(self):
|
||||||
"""Return running state of addon."""
|
"""Return running state of add-on."""
|
||||||
if not self.is_installed:
|
if not self.is_installed:
|
||||||
return STATE_NONE
|
return STATE_NONE
|
||||||
|
|
||||||
@ -703,9 +728,9 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""Set options and start addon."""
|
"""Set options and start add-on."""
|
||||||
if await self.instance.is_running():
|
if await self.instance.is_running():
|
||||||
_LOGGER.warning("%s allready running!", self.slug)
|
_LOGGER.warning("%s already running!", self.slug)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Access Token
|
# Access Token
|
||||||
@ -724,7 +749,7 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Stop addon.
|
"""Stop add-on.
|
||||||
|
|
||||||
Return a coroutine.
|
Return a coroutine.
|
||||||
"""
|
"""
|
||||||
@ -732,11 +757,11 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def update(self):
|
async def update(self):
|
||||||
"""Update addon."""
|
"""Update add-on."""
|
||||||
last_state = await self.state()
|
last_state = await self.state()
|
||||||
|
|
||||||
if self.last_version == self.version_installed:
|
if self.last_version == self.version_installed:
|
||||||
_LOGGER.warning("No update available for Addon %s", self._id)
|
_LOGGER.warning("No update available for add-on %s", self._id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not await self.instance.update(self.last_version):
|
if not await self.instance.update(self.last_version):
|
||||||
@ -753,13 +778,13 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def restart(self):
|
async def restart(self):
|
||||||
"""Restart addon."""
|
"""Restart add-on."""
|
||||||
await self.stop()
|
await self.stop()
|
||||||
return await self.start()
|
return await self.start()
|
||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
def logs(self):
|
def logs(self):
|
||||||
"""Return addons log output.
|
"""Return add-ons log output.
|
||||||
|
|
||||||
Return a coroutine.
|
Return a coroutine.
|
||||||
"""
|
"""
|
||||||
@ -775,11 +800,11 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def rebuild(self):
|
async def rebuild(self):
|
||||||
"""Performe a rebuild of local build addon."""
|
"""Perform a rebuild of local build add-on."""
|
||||||
last_state = await self.state()
|
last_state = await self.state()
|
||||||
|
|
||||||
if not self.need_build:
|
if not self.need_build:
|
||||||
_LOGGER.error("Can't rebuild a none local build addon!")
|
_LOGGER.error("Can't rebuild a none local build add-on!")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# remove docker container but not addon config
|
# remove docker container but not addon config
|
||||||
@ -808,7 +833,7 @@ class Addon(CoreSysAttributes):
|
|||||||
|
|
||||||
@check_installed
|
@check_installed
|
||||||
async def snapshot(self, tar_file):
|
async def snapshot(self, tar_file):
|
||||||
"""Snapshot state of an addon."""
|
"""Snapshot state of an add-on."""
|
||||||
with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp:
|
with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp:
|
||||||
# store local image
|
# store local image
|
||||||
if self.need_build and not await \
|
if self.need_build and not await \
|
||||||
@ -822,7 +847,7 @@ class Addon(CoreSysAttributes):
|
|||||||
ATTR_STATE: await self.state(),
|
ATTR_STATE: await self.state(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# store local configs/state
|
# Store local configs/state
|
||||||
try:
|
try:
|
||||||
write_json_file(Path(temp, 'addon.json'), data)
|
write_json_file(Path(temp, 'addon.json'), data)
|
||||||
except (OSError, json.JSONDecodeError) as err:
|
except (OSError, json.JSONDecodeError) as err:
|
||||||
@ -846,7 +871,7 @@ class Addon(CoreSysAttributes):
|
|||||||
snapshot.add(self.path_data, arcname="data")
|
snapshot.add(self.path_data, arcname="data")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_LOGGER.info("Build snapshot for addon %s", self._id)
|
_LOGGER.info("Build snapshot for add-on %s", self._id)
|
||||||
await self.sys_run_in_executor(_write_tarfile)
|
await self.sys_run_in_executor(_write_tarfile)
|
||||||
except (tarfile.TarError, OSError) as err:
|
except (tarfile.TarError, OSError) as err:
|
||||||
_LOGGER.error("Can't write tarfile %s: %s", tar_file, err)
|
_LOGGER.error("Can't write tarfile %s: %s", tar_file, err)
|
||||||
@ -856,7 +881,7 @@ class Addon(CoreSysAttributes):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
async def restore(self, tar_file):
|
async def restore(self, tar_file):
|
||||||
"""Restore state of an addon."""
|
"""Restore state of an add-on."""
|
||||||
with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp:
|
with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp:
|
||||||
# extract snapshot
|
# extract snapshot
|
||||||
def _extract_tarfile():
|
def _extract_tarfile():
|
||||||
@ -870,13 +895,13 @@ class Addon(CoreSysAttributes):
|
|||||||
_LOGGER.error("Can't read tarfile %s: %s", tar_file, err)
|
_LOGGER.error("Can't read tarfile %s: %s", tar_file, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# read snapshot data
|
# Read snapshot data
|
||||||
try:
|
try:
|
||||||
data = read_json_file(Path(temp, 'addon.json'))
|
data = read_json_file(Path(temp, 'addon.json'))
|
||||||
except (OSError, json.JSONDecodeError) as err:
|
except (OSError, json.JSONDecodeError) as err:
|
||||||
_LOGGER.error("Can't read addon.json: %s", err)
|
_LOGGER.error("Can't read addon.json: %s", err)
|
||||||
|
|
||||||
# validate
|
# Validate
|
||||||
try:
|
try:
|
||||||
data = SCHEMA_ADDON_SNAPSHOT(data)
|
data = SCHEMA_ADDON_SNAPSHOT(data)
|
||||||
except vol.Invalid as err:
|
except vol.Invalid as err:
|
||||||
@ -884,11 +909,11 @@ class Addon(CoreSysAttributes):
|
|||||||
self._id, humanize_error(data, err))
|
self._id, humanize_error(data, err))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# restore data / reload addon
|
# Restore data or reload add-on
|
||||||
_LOGGER.info("Restore config for addon %s", self._id)
|
_LOGGER.info("Restore config for addon %s", self._id)
|
||||||
self._restore_data(data[ATTR_USER], data[ATTR_SYSTEM])
|
self._restore_data(data[ATTR_USER], data[ATTR_SYSTEM])
|
||||||
|
|
||||||
# check version / restore image
|
# Check version / restore image
|
||||||
version = data[ATTR_VERSION]
|
version = data[ATTR_VERSION]
|
||||||
if not await self.instance.exists():
|
if not await self.instance.exists():
|
||||||
_LOGGER.info("Restore image for addon %s", self._id)
|
_LOGGER.info("Restore image for addon %s", self._id)
|
||||||
@ -902,7 +927,7 @@ class Addon(CoreSysAttributes):
|
|||||||
else:
|
else:
|
||||||
await self.instance.stop()
|
await self.instance.stop()
|
||||||
|
|
||||||
# restore data
|
# Restore data
|
||||||
def _restore_data():
|
def _restore_data():
|
||||||
"""Restore data."""
|
"""Restore data."""
|
||||||
shutil.copytree(str(Path(temp, "data")), str(self.path_data))
|
shutil.copytree(str(Path(temp, "data")), str(self.path_data))
|
||||||
@ -926,9 +951,9 @@ class Addon(CoreSysAttributes):
|
|||||||
_LOGGER.error("Can't restore AppArmor profile")
|
_LOGGER.error("Can't restore AppArmor profile")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# run addon
|
# Run add-on
|
||||||
if data[ATTR_STATE] == STATE_STARTED:
|
if data[ATTR_STATE] == STATE_STARTED:
|
||||||
return await self.start()
|
return await self.start()
|
||||||
|
|
||||||
_LOGGER.info("Finish restore for addon %s", self._id)
|
_LOGGER.info("Finish restore for add-on %s", self._id)
|
||||||
return True
|
return True
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""HassIO addons build environment."""
|
"""Hass.io add-on build environment."""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .validate import SCHEMA_BUILD_CONFIG, BASE_IMAGE
|
from .validate import SCHEMA_BUILD_CONFIG, BASE_IMAGE
|
||||||
@ -8,10 +8,10 @@ from ..utils.json import JsonConfig
|
|||||||
|
|
||||||
|
|
||||||
class AddonBuild(JsonConfig, CoreSysAttributes):
|
class AddonBuild(JsonConfig, CoreSysAttributes):
|
||||||
"""Handle build options for addons."""
|
"""Handle build options for add-ons."""
|
||||||
|
|
||||||
def __init__(self, coresys, slug):
|
def __init__(self, coresys, slug):
|
||||||
"""Initialize addon builder."""
|
"""Initialize Hass.io add-on builder."""
|
||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
self._id = slug
|
self._id = slug
|
||||||
|
|
||||||
@ -24,12 +24,12 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def addon(self):
|
def addon(self):
|
||||||
"""Return addon of build data."""
|
"""Return add-on of build data."""
|
||||||
return self.sys_addons.get(self._id)
|
return self.sys_addons.get(self._id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base_image(self):
|
def base_image(self):
|
||||||
"""Base images for this addon."""
|
"""Base images for this add-on."""
|
||||||
return self._data[ATTR_BUILD_FROM].get(
|
return self._data[ATTR_BUILD_FROM].get(
|
||||||
self.sys_arch, BASE_IMAGE[self.sys_arch])
|
self.sys_arch, BASE_IMAGE[self.sys_arch])
|
||||||
|
|
||||||
@ -40,11 +40,11 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def additional_args(self):
|
def additional_args(self):
|
||||||
"""Return additional docker build arguments."""
|
"""Return additional Docker build arguments."""
|
||||||
return self._data[ATTR_ARGS]
|
return self._data[ATTR_ARGS]
|
||||||
|
|
||||||
def get_docker_args(self, version):
|
def get_docker_args(self, version):
|
||||||
"""Create a dict with docker build arguments."""
|
"""Create a dict with Docker build arguments."""
|
||||||
args = {
|
args = {
|
||||||
'path': str(self.addon.path_location),
|
'path': str(self.addon.path_location),
|
||||||
'tag': f"{self.addon.image}:{version}",
|
'tag': f"{self.addon.image}:{version}",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO addons."""
|
"""Init file for Hass.io add-on data."""
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class AddonsData(JsonConfig, CoreSysAttributes):
|
class AddonsData(JsonConfig, CoreSysAttributes):
|
||||||
"""Hold data for addons inside HassIO."""
|
"""Hold data for Add-ons inside Hass.io."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys):
|
||||||
"""Initialize data holder."""
|
"""Initialize data holder."""
|
||||||
@ -30,26 +30,26 @@ class AddonsData(JsonConfig, CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def user(self):
|
def user(self):
|
||||||
"""Return local addon user data."""
|
"""Return local add-on user data."""
|
||||||
return self._data[ATTR_USER]
|
return self._data[ATTR_USER]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def system(self):
|
def system(self):
|
||||||
"""Return local addon data."""
|
"""Return local add-on data."""
|
||||||
return self._data[ATTR_SYSTEM]
|
return self._data[ATTR_SYSTEM]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cache(self):
|
def cache(self):
|
||||||
"""Return addon data from cache/repositories."""
|
"""Return add-on data from cache/repositories."""
|
||||||
return self._cache
|
return self._cache
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def repositories(self):
|
def repositories(self):
|
||||||
"""Return addon data from repositories."""
|
"""Return add-on data from repositories."""
|
||||||
return self._repositories
|
return self._repositories
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
||||||
"""Read data from addons repository."""
|
"""Read data from add-on repository."""
|
||||||
self._cache = {}
|
self._cache = {}
|
||||||
self._repositories = {}
|
self._repositories = {}
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ class AddonsData(JsonConfig, CoreSysAttributes):
|
|||||||
self._read_addons_folder(path, slug)
|
self._read_addons_folder(path, slug)
|
||||||
|
|
||||||
def _read_addons_folder(self, path, repository):
|
def _read_addons_folder(self, path, repository):
|
||||||
"""Read data from addons folder."""
|
"""Read data from add-ons folder."""
|
||||||
for addon in path.glob("**/config.json"):
|
for addon in path.glob("**/config.json"):
|
||||||
try:
|
try:
|
||||||
addon_config = read_json_file(addon)
|
addon_config = read_json_file(addon)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO addons git."""
|
"""Init file for Hass.io add-on Git."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import functools as ft
|
import functools as ft
|
||||||
@ -16,10 +16,10 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class GitRepo(CoreSysAttributes):
|
class GitRepo(CoreSysAttributes):
|
||||||
"""Manage addons git repo."""
|
"""Manage Add-on Git repository."""
|
||||||
|
|
||||||
def __init__(self, coresys, path, url):
|
def __init__(self, coresys, path, url):
|
||||||
"""Initialize git base wrapper."""
|
"""Initialize Git base wrapper."""
|
||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
self.repo = None
|
self.repo = None
|
||||||
self.path = path
|
self.path = path
|
||||||
@ -38,13 +38,13 @@ class GitRepo(CoreSysAttributes):
|
|||||||
return self._data[ATTR_BRANCH]
|
return self._data[ATTR_BRANCH]
|
||||||
|
|
||||||
async def load(self):
|
async def load(self):
|
||||||
"""Init git addon repo."""
|
"""Init Git add-on repository."""
|
||||||
if not self.path.is_dir():
|
if not self.path.is_dir():
|
||||||
return await self.clone()
|
return await self.clone()
|
||||||
|
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
try:
|
try:
|
||||||
_LOGGER.info("Load addon %s repository", self.path)
|
_LOGGER.info("Load add-on %s repository", self.path)
|
||||||
self.repo = await self.sys_run_in_executor(
|
self.repo = await self.sys_run_in_executor(
|
||||||
git.Repo, str(self.path))
|
git.Repo, str(self.path))
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ class GitRepo(CoreSysAttributes):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
async def clone(self):
|
async def clone(self):
|
||||||
"""Clone git addon repo."""
|
"""Clone git add-on repository."""
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
git_args = {
|
git_args = {
|
||||||
attribute: value
|
attribute: value
|
||||||
@ -70,7 +70,7 @@ class GitRepo(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_LOGGER.info("Clone addon %s repository", self.url)
|
_LOGGER.info("Clone add-on %s repository", self.url)
|
||||||
self.repo = await self.sys_run_in_executor(ft.partial(
|
self.repo = await self.sys_run_in_executor(ft.partial(
|
||||||
git.Repo.clone_from, self.url, str(self.path),
|
git.Repo.clone_from, self.url, str(self.path),
|
||||||
**git_args
|
**git_args
|
||||||
@ -78,20 +78,20 @@ class GitRepo(CoreSysAttributes):
|
|||||||
|
|
||||||
except (git.InvalidGitRepositoryError, git.NoSuchPathError,
|
except (git.InvalidGitRepositoryError, git.NoSuchPathError,
|
||||||
git.GitCommandError) as err:
|
git.GitCommandError) as err:
|
||||||
_LOGGER.error("Can't clone %s repo: %s.", self.url, err)
|
_LOGGER.error("Can't clone %s repository: %s.", self.url, err)
|
||||||
self._remove()
|
self._remove()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def pull(self):
|
async def pull(self):
|
||||||
"""Pull git addon repo."""
|
"""Pull Git add-on repo."""
|
||||||
if self.lock.locked():
|
if self.lock.locked():
|
||||||
_LOGGER.warning("It is already a task in progress.")
|
_LOGGER.warning("It is already a task in progress")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
_LOGGER.info("Update addon %s repository", self.url)
|
_LOGGER.info("Update add-on %s repository", self.url)
|
||||||
branch = self.repo.active_branch.name
|
branch = self.repo.active_branch.name
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -130,19 +130,19 @@ class GitRepo(CoreSysAttributes):
|
|||||||
|
|
||||||
|
|
||||||
class GitRepoHassIO(GitRepo):
|
class GitRepoHassIO(GitRepo):
|
||||||
"""HassIO addons repository."""
|
"""Hass.io add-ons repository."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys):
|
||||||
"""Initialize git hassio addon repository."""
|
"""Initialize Git Hass.io add-on repository."""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
coresys, coresys.config.path_addons_core, URL_HASSIO_ADDONS)
|
coresys, coresys.config.path_addons_core, URL_HASSIO_ADDONS)
|
||||||
|
|
||||||
|
|
||||||
class GitRepoCustom(GitRepo):
|
class GitRepoCustom(GitRepo):
|
||||||
"""Custom addons repository."""
|
"""Custom add-ons repository."""
|
||||||
|
|
||||||
def __init__(self, coresys, url):
|
def __init__(self, coresys, url):
|
||||||
"""Initialize git hassio addon repository."""
|
"""Initialize custom Git Hass.io addo-n repository."""
|
||||||
path = Path(
|
path = Path(
|
||||||
coresys.config.path_addons_git,
|
coresys.config.path_addons_git,
|
||||||
get_hash_from_repository(url))
|
get_hash_from_repository(url))
|
||||||
@ -151,5 +151,5 @@ class GitRepoCustom(GitRepo):
|
|||||||
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
"""Remove a custom repository."""
|
"""Remove a custom repository."""
|
||||||
_LOGGER.info("Remove custom addon repository %s", self.url)
|
_LOGGER.info("Remove custom add-on repository %s", self.url)
|
||||||
self._remove()
|
self._remove()
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
"""Represent a HassIO repository."""
|
"""Represent a Hass.io repository."""
|
||||||
from .git import GitRepoHassIO, GitRepoCustom
|
from .git import GitRepoHassIO, GitRepoCustom
|
||||||
from .utils import get_hash_from_repository
|
from .utils import get_hash_from_repository
|
||||||
from ..const import (
|
from ..const import (
|
||||||
REPOSITORY_CORE, REPOSITORY_LOCAL, ATTR_NAME, ATTR_URL, ATTR_MAINTAINER)
|
REPOSITORY_CORE, REPOSITORY_LOCAL, ATTR_NAME, ATTR_URL, ATTR_MAINTAINER)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..exceptions import APIError
|
||||||
|
|
||||||
UNKNOWN = 'unknown'
|
UNKNOWN = 'unknown'
|
||||||
|
|
||||||
|
|
||||||
class Repository(CoreSysAttributes):
|
class Repository(CoreSysAttributes):
|
||||||
"""Repository in HassIO."""
|
"""Repository in Hass.io."""
|
||||||
|
|
||||||
def __init__(self, coresys, repository):
|
def __init__(self, coresys, repository):
|
||||||
"""Initialize repository object."""
|
"""Initialize repository object."""
|
||||||
@ -44,7 +45,7 @@ class Repository(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self):
|
def url(self):
|
||||||
"""Return url of repository."""
|
"""Return URL of repository."""
|
||||||
return self._mesh.get(ATTR_URL, self.source)
|
return self._mesh.get(ATTR_URL, self.source)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -59,14 +60,14 @@ class Repository(CoreSysAttributes):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
async def update(self):
|
async def update(self):
|
||||||
"""Update addon repository."""
|
"""Update add-on repository."""
|
||||||
if self.git:
|
if self.git:
|
||||||
return await self.git.pull()
|
return await self.git.pull()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
"""Remove addon repository."""
|
"""Remove add-on repository."""
|
||||||
if self._id in (REPOSITORY_CORE, REPOSITORY_LOCAL):
|
if self._id in (REPOSITORY_CORE, REPOSITORY_LOCAL):
|
||||||
raise RuntimeError("Can't remove built-in repositories!")
|
raise APIError("Can't remove built-in repositories!")
|
||||||
|
|
||||||
self.git.remove()
|
self.git.remove()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Util addons functions."""
|
"""Util add-ons functions."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
@ -28,10 +28,6 @@ def rating_security(addon):
|
|||||||
elif addon.apparmor == SECURITY_PROFILE:
|
elif addon.apparmor == SECURITY_PROFILE:
|
||||||
rating += 1
|
rating += 1
|
||||||
|
|
||||||
# API Access
|
|
||||||
if addon.access_hassio_api or addon.access_homeassistant_api:
|
|
||||||
rating += -1
|
|
||||||
|
|
||||||
# Privileged options
|
# Privileged options
|
||||||
if addon.privileged in (PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN,
|
if addon.privileged in (PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN,
|
||||||
PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE):
|
PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE):
|
||||||
@ -78,7 +74,7 @@ def extract_hash_from_path(path):
|
|||||||
|
|
||||||
|
|
||||||
def check_installed(method):
|
def check_installed(method):
|
||||||
"""Wrap function with check if addon is installed."""
|
"""Wrap function with check if add-on is installed."""
|
||||||
async def wrap_check(addon, *args, **kwargs):
|
async def wrap_check(addon, *args, **kwargs):
|
||||||
"""Return False if not installed or the function."""
|
"""Return False if not installed or the function."""
|
||||||
if not addon.is_installed:
|
if not addon.is_installed:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Validate addons options schema."""
|
"""Validate add-ons options schema."""
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
@ -20,18 +20,19 @@ from ..const import (
|
|||||||
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY,
|
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY,
|
||||||
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED,
|
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED,
|
||||||
ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE,
|
ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE,
|
||||||
|
ATTR_MACHINE,
|
||||||
PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO,
|
PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO,
|
||||||
PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE,
|
PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE,
|
||||||
PRIVILEGED_SYS_RESOURCE, PRIVILEGED_SYS_PTRACE,
|
PRIVILEGED_SYS_RESOURCE, PRIVILEGED_SYS_PTRACE,
|
||||||
ROLE_DEFAULT, ROLE_HOMEASSISTANT, ROLE_MANAGER, ROLE_ADMIN)
|
ROLE_DEFAULT, ROLE_HOMEASSISTANT, ROLE_MANAGER, ROLE_ADMIN)
|
||||||
from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_DEVICE
|
from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_DEVICE, UUID_MATCH
|
||||||
|
from ..services.validate import DISCOVERY_SERVICES
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
RE_VOLUME = re.compile(r"^(config|ssl|addons|backup|share)(?::(rw|:ro))?$")
|
RE_VOLUME = re.compile(r"^(config|ssl|addons|backup|share)(?::(rw|:ro))?$")
|
||||||
RE_SERVICE = re.compile(r"^(?P<service>mqtt)(?::(?P<rights>rw|:ro))?$")
|
RE_SERVICE = re.compile(r"^(?P<service>mqtt):(?P<rights>provide|want|need)$")
|
||||||
RE_DISCOVERY = re.compile(r"^(?P<component>\w*)(?:/(?P<platform>\w*>))?$")
|
|
||||||
|
|
||||||
V_STR = 'str'
|
V_STR = 'str'
|
||||||
V_INT = 'int'
|
V_INT = 'int'
|
||||||
@ -57,6 +58,12 @@ ARCH_ALL = [
|
|||||||
ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, ARCH_I386
|
ARCH_ARMHF, ARCH_AARCH64, ARCH_AMD64, ARCH_I386
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MACHINE_ALL = [
|
||||||
|
'intel-nuc', 'qemux86', 'qemux86-64', 'qemuarm', 'qemuarm-64',
|
||||||
|
'raspberrypi', 'raspberrypi2', 'raspberrypi3', 'raspberrypi3-64',
|
||||||
|
'odroid-cu2', 'odroid-xu',
|
||||||
|
]
|
||||||
|
|
||||||
STARTUP_ALL = [
|
STARTUP_ALL = [
|
||||||
STARTUP_ONCE, STARTUP_INITIALIZE, STARTUP_SYSTEM, STARTUP_SERVICES,
|
STARTUP_ONCE, STARTUP_INITIALIZE, STARTUP_SYSTEM, STARTUP_SERVICES,
|
||||||
STARTUP_APPLICATION
|
STARTUP_APPLICATION
|
||||||
@ -105,6 +112,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
vol.Required(ATTR_DESCRIPTON): vol.Coerce(str),
|
vol.Required(ATTR_DESCRIPTON): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_URL): vol.Url(),
|
vol.Optional(ATTR_URL): vol.Url(),
|
||||||
vol.Optional(ATTR_ARCH, default=ARCH_ALL): [vol.In(ARCH_ALL)],
|
vol.Optional(ATTR_ARCH, default=ARCH_ALL): [vol.In(ARCH_ALL)],
|
||||||
|
vol.Optional(ATTR_MACHINE): [vol.In(MACHINE_ALL)],
|
||||||
vol.Required(ATTR_STARTUP):
|
vol.Required(ATTR_STARTUP):
|
||||||
vol.All(_simple_startup, vol.In(STARTUP_ALL)),
|
vol.All(_simple_startup, vol.In(STARTUP_ALL)),
|
||||||
vol.Required(ATTR_BOOT):
|
vol.Required(ATTR_BOOT):
|
||||||
@ -135,7 +143,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
vol.Optional(ATTR_LEGACY, default=False): vol.Boolean(),
|
vol.Optional(ATTR_LEGACY, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_DOCKER_API, default=False): vol.Boolean(),
|
vol.Optional(ATTR_DOCKER_API, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_SERVICES): [vol.Match(RE_SERVICE)],
|
vol.Optional(ATTR_SERVICES): [vol.Match(RE_SERVICE)],
|
||||||
vol.Optional(ATTR_DISCOVERY): [vol.Match(RE_DISCOVERY)],
|
vol.Optional(ATTR_DISCOVERY): [vol.In(DISCOVERY_SERVICES)],
|
||||||
vol.Required(ATTR_OPTIONS): dict,
|
vol.Required(ATTR_OPTIONS): dict,
|
||||||
vol.Required(ATTR_SCHEMA): vol.Any(vol.Schema({
|
vol.Required(ATTR_SCHEMA): vol.Any(vol.Schema({
|
||||||
vol.Coerce(str): vol.Any(SCHEMA_ELEMENT, [
|
vol.Coerce(str): vol.Any(SCHEMA_ELEMENT, [
|
||||||
@ -177,8 +185,7 @@ SCHEMA_BUILD_CONFIG = vol.Schema({
|
|||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_ADDON_USER = vol.Schema({
|
SCHEMA_ADDON_USER = vol.Schema({
|
||||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
|
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex): UUID_MATCH,
|
||||||
vol.Match(r"^[0-9a-f]{32}$"),
|
|
||||||
vol.Optional(ATTR_ACCESS_TOKEN): vol.Match(r"^[0-9a-f]{64}$"),
|
vol.Optional(ATTR_ACCESS_TOKEN): vol.Match(r"^[0-9a-f]{64}$"),
|
||||||
vol.Optional(ATTR_OPTIONS, default=dict): dict,
|
vol.Optional(ATTR_OPTIONS, default=dict): dict,
|
||||||
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
|
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
|
||||||
@ -218,7 +225,7 @@ SCHEMA_ADDON_SNAPSHOT = vol.Schema({
|
|||||||
def validate_options(raw_schema):
|
def validate_options(raw_schema):
|
||||||
"""Validate schema."""
|
"""Validate schema."""
|
||||||
def validate(struct):
|
def validate(struct):
|
||||||
"""Create schema validator for addons options."""
|
"""Create schema validator for add-ons options."""
|
||||||
options = {}
|
options = {}
|
||||||
|
|
||||||
# read options
|
# read options
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO rest api."""
|
"""Init file for Hass.io RESTful API."""
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@ -14,6 +14,7 @@ from .proxy import APIProxy
|
|||||||
from .supervisor import APISupervisor
|
from .supervisor import APISupervisor
|
||||||
from .snapshots import APISnapshots
|
from .snapshots import APISnapshots
|
||||||
from .services import APIServices
|
from .services import APIServices
|
||||||
|
from .version import APIVersion
|
||||||
from .security import SecurityMiddleware
|
from .security import SecurityMiddleware
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
|
||||||
@ -21,10 +22,10 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class RestAPI(CoreSysAttributes):
|
class RestAPI(CoreSysAttributes):
|
||||||
"""Handle rest api for hassio."""
|
"""Handle RESTful API for Hass.io."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys):
|
||||||
"""Initialize docker base wrapper."""
|
"""Initialize Docker base wrapper."""
|
||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
self.security = SecurityMiddleware(coresys)
|
self.security = SecurityMiddleware(coresys)
|
||||||
self.webapp = web.Application(
|
self.webapp = web.Application(
|
||||||
@ -47,9 +48,10 @@ class RestAPI(CoreSysAttributes):
|
|||||||
self._register_snapshots()
|
self._register_snapshots()
|
||||||
self._register_discovery()
|
self._register_discovery()
|
||||||
self._register_services()
|
self._register_services()
|
||||||
|
self._register_version()
|
||||||
|
|
||||||
def _register_host(self):
|
def _register_host(self):
|
||||||
"""Register hostcontrol function."""
|
"""Register hostcontrol functions."""
|
||||||
api_host = APIHost()
|
api_host = APIHost()
|
||||||
api_host.coresys = self.coresys
|
api_host.coresys = self.coresys
|
||||||
|
|
||||||
@ -69,7 +71,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
])
|
])
|
||||||
|
|
||||||
def _register_hassos(self):
|
def _register_hassos(self):
|
||||||
"""Register hassos function."""
|
"""Register HassOS functions."""
|
||||||
api_hassos = APIHassOS()
|
api_hassos = APIHassOS()
|
||||||
api_hassos.coresys = self.coresys
|
api_hassos.coresys = self.coresys
|
||||||
|
|
||||||
@ -81,7 +83,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
])
|
])
|
||||||
|
|
||||||
def _register_hardware(self):
|
def _register_hardware(self):
|
||||||
"""Register hardware function."""
|
"""Register hardware functions."""
|
||||||
api_hardware = APIHardware()
|
api_hardware = APIHardware()
|
||||||
api_hardware.coresys = self.coresys
|
api_hardware.coresys = self.coresys
|
||||||
|
|
||||||
@ -90,8 +92,17 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get('/hardware/audio', api_hardware.audio),
|
web.get('/hardware/audio', api_hardware.audio),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def _register_version(self):
|
||||||
|
"""Register version functions."""
|
||||||
|
api_version = APIVersion()
|
||||||
|
api_version.coresys = self.coresys
|
||||||
|
|
||||||
|
self.webapp.add_routes([
|
||||||
|
web.get('/version', api_version.info),
|
||||||
|
])
|
||||||
|
|
||||||
def _register_supervisor(self):
|
def _register_supervisor(self):
|
||||||
"""Register supervisor function."""
|
"""Register Supervisor functions."""
|
||||||
api_supervisor = APISupervisor()
|
api_supervisor = APISupervisor()
|
||||||
api_supervisor.coresys = self.coresys
|
api_supervisor.coresys = self.coresys
|
||||||
|
|
||||||
@ -106,7 +117,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
])
|
])
|
||||||
|
|
||||||
def _register_homeassistant(self):
|
def _register_homeassistant(self):
|
||||||
"""Register homeassistant function."""
|
"""Register Home Assistant functions."""
|
||||||
api_hass = APIHomeAssistant()
|
api_hass = APIHomeAssistant()
|
||||||
api_hass.coresys = self.coresys
|
api_hass.coresys = self.coresys
|
||||||
|
|
||||||
@ -137,7 +148,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
])
|
])
|
||||||
|
|
||||||
def _register_addons(self):
|
def _register_addons(self):
|
||||||
"""Register homeassistant function."""
|
"""Register Add-on functions."""
|
||||||
api_addons = APIAddons()
|
api_addons = APIAddons()
|
||||||
api_addons.coresys = self.coresys
|
api_addons.coresys = self.coresys
|
||||||
|
|
||||||
@ -163,7 +174,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
])
|
])
|
||||||
|
|
||||||
def _register_snapshots(self):
|
def _register_snapshots(self):
|
||||||
"""Register snapshots function."""
|
"""Register snapshots functions."""
|
||||||
api_snapshots = APISnapshots()
|
api_snapshots = APISnapshots()
|
||||||
api_snapshots.coresys = self.coresys
|
api_snapshots.coresys = self.coresys
|
||||||
|
|
||||||
@ -183,6 +194,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
])
|
])
|
||||||
|
|
||||||
def _register_services(self):
|
def _register_services(self):
|
||||||
|
"""Register services functions."""
|
||||||
api_services = APIServices()
|
api_services = APIServices()
|
||||||
api_services.coresys = self.coresys
|
api_services.coresys = self.coresys
|
||||||
|
|
||||||
@ -194,19 +206,20 @@ class RestAPI(CoreSysAttributes):
|
|||||||
])
|
])
|
||||||
|
|
||||||
def _register_discovery(self):
|
def _register_discovery(self):
|
||||||
|
"""Register discovery functions."""
|
||||||
api_discovery = APIDiscovery()
|
api_discovery = APIDiscovery()
|
||||||
api_discovery.coresys = self.coresys
|
api_discovery.coresys = self.coresys
|
||||||
|
|
||||||
self.webapp.add_routes([
|
self.webapp.add_routes([
|
||||||
web.get('/services/discovery', api_discovery.list),
|
web.get('/discovery', api_discovery.list),
|
||||||
web.get('/services/discovery/{uuid}', api_discovery.get_discovery),
|
web.get('/discovery/{uuid}', api_discovery.get_discovery),
|
||||||
web.delete('/services/discovery/{uuid}',
|
web.delete('/discovery/{uuid}',
|
||||||
api_discovery.del_discovery),
|
api_discovery.del_discovery),
|
||||||
web.post('/services/discovery', api_discovery.set_discovery),
|
web.post('/discovery', api_discovery.set_discovery),
|
||||||
])
|
])
|
||||||
|
|
||||||
def _register_panel(self):
|
def _register_panel(self):
|
||||||
"""Register panel for homeassistant."""
|
"""Register panel for Home Assistant."""
|
||||||
panel_dir = Path(__file__).parent.joinpath("panel")
|
panel_dir = Path(__file__).parent.joinpath("panel")
|
||||||
|
|
||||||
def create_response(panel_file):
|
def create_response(panel_file):
|
||||||
@ -234,7 +247,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
self.webapp.add_routes([web.static('/app', panel_dir)])
|
self.webapp.add_routes([web.static('/app', panel_dir)])
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""Run rest api webserver."""
|
"""Run RESTful API webserver."""
|
||||||
await self._runner.setup()
|
await self._runner.setup()
|
||||||
self._site = web.TCPSite(
|
self._site = web.TCPSite(
|
||||||
self._runner, host="0.0.0.0", port=80, shutdown_timeout=5)
|
self._runner, host="0.0.0.0", port=80, shutdown_timeout=5)
|
||||||
@ -248,7 +261,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
_LOGGER.info("Start API on %s", self.sys_docker.network.supervisor)
|
_LOGGER.info("Start API on %s", self.sys_docker.network.supervisor)
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self):
|
||||||
"""Stop rest api webserver."""
|
"""Stop RESTful API webserver."""
|
||||||
if not self._site:
|
if not self._site:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO homeassistant rest api."""
|
"""Init file for Hass.io Home Assistant RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -20,11 +20,10 @@ from ..const import (
|
|||||||
ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES,
|
ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES,
|
||||||
ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API,
|
ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API,
|
||||||
ATTR_FULL_ACCESS, ATTR_PROTECTED, ATTR_RATING, ATTR_HOST_PID,
|
ATTR_FULL_ACCESS, ATTR_PROTECTED, ATTR_RATING, ATTR_HOST_PID,
|
||||||
ATTR_HASSIO_ROLE,
|
ATTR_HASSIO_ROLE, ATTR_MACHINE, ATTR_AVAILABLE,
|
||||||
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT, REQUEST_FROM)
|
CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT, REQUEST_FROM)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..validate import DOCKER_PORTS, ALSA_DEVICE
|
from ..validate import DOCKER_PORTS, ALSA_DEVICE
|
||||||
from ..exceptions import APINotSupportedError
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -48,7 +47,7 @@ SCHEMA_SECURITY = vol.Schema({
|
|||||||
|
|
||||||
|
|
||||||
class APIAddons(CoreSysAttributes):
|
class APIAddons(CoreSysAttributes):
|
||||||
"""Handle rest api for addons functions."""
|
"""Handle RESTful API for add-on functions."""
|
||||||
|
|
||||||
def _extract_addon(self, request, check_installed=True):
|
def _extract_addon(self, request, check_installed=True):
|
||||||
"""Return addon, throw an exception it it doesn't exist."""
|
"""Return addon, throw an exception it it doesn't exist."""
|
||||||
@ -56,7 +55,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
# Lookup itself
|
# Lookup itself
|
||||||
if addon_slug == 'self':
|
if addon_slug == 'self':
|
||||||
addon_slug = request.get(REQUEST_FROM)
|
return request.get(REQUEST_FROM)
|
||||||
|
|
||||||
addon = self.sys_addons.get(addon_slug)
|
addon = self.sys_addons.get(addon_slug)
|
||||||
if not addon:
|
if not addon:
|
||||||
@ -67,17 +66,9 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
return addon
|
return addon
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _pretty_devices(addon):
|
|
||||||
"""Return a simplified device list."""
|
|
||||||
dev_list = addon.devices
|
|
||||||
if not dev_list:
|
|
||||||
return None
|
|
||||||
return [row.split(':')[0] for row in dev_list]
|
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def list(self, request):
|
async def list(self, request):
|
||||||
"""Return all addons / repositories ."""
|
"""Return all add-ons or repositories."""
|
||||||
data_addons = []
|
data_addons = []
|
||||||
for addon in self.sys_addons.list_addons:
|
for addon in self.sys_addons.list_addons:
|
||||||
data_addons.append({
|
data_addons.append({
|
||||||
@ -86,7 +77,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_DESCRIPTON: addon.description,
|
ATTR_DESCRIPTON: addon.description,
|
||||||
ATTR_VERSION: addon.last_version,
|
ATTR_VERSION: addon.last_version,
|
||||||
ATTR_INSTALLED: addon.version_installed,
|
ATTR_INSTALLED: addon.version_installed,
|
||||||
ATTR_ARCH: addon.supported_arch,
|
ATTR_AVAILABLE: addon.available,
|
||||||
ATTR_DETACHED: addon.is_detached,
|
ATTR_DETACHED: addon.is_detached,
|
||||||
ATTR_REPOSITORY: addon.repository,
|
ATTR_REPOSITORY: addon.repository,
|
||||||
ATTR_BUILD: addon.need_build,
|
ATTR_BUILD: addon.need_build,
|
||||||
@ -112,13 +103,13 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def reload(self, request):
|
async def reload(self, request):
|
||||||
"""Reload all addons data."""
|
"""Reload all add-on data."""
|
||||||
await asyncio.shield(self.sys_addons.reload())
|
await asyncio.shield(self.sys_addons.reload())
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request):
|
async def info(self, request):
|
||||||
"""Return addon information."""
|
"""Return add-on information."""
|
||||||
addon = self._extract_addon(request, check_installed=False)
|
addon = self._extract_addon(request, check_installed=False)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -135,8 +126,11 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_RATING: rating_security(addon),
|
ATTR_RATING: rating_security(addon),
|
||||||
ATTR_BOOT: addon.boot,
|
ATTR_BOOT: addon.boot,
|
||||||
ATTR_OPTIONS: addon.options,
|
ATTR_OPTIONS: addon.options,
|
||||||
|
ATTR_ARCH: addon.supported_arch,
|
||||||
|
ATTR_MACHINE: addon.supported_machine,
|
||||||
ATTR_URL: addon.url,
|
ATTR_URL: addon.url,
|
||||||
ATTR_DETACHED: addon.is_detached,
|
ATTR_DETACHED: addon.is_detached,
|
||||||
|
ATTR_AVAILABLE: addon.available,
|
||||||
ATTR_BUILD: addon.need_build,
|
ATTR_BUILD: addon.need_build,
|
||||||
ATTR_NETWORK: addon.ports,
|
ATTR_NETWORK: addon.ports,
|
||||||
ATTR_HOST_NETWORK: addon.host_network,
|
ATTR_HOST_NETWORK: addon.host_network,
|
||||||
@ -146,7 +140,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_PRIVILEGED: addon.privileged,
|
ATTR_PRIVILEGED: addon.privileged,
|
||||||
ATTR_FULL_ACCESS: addon.with_full_access,
|
ATTR_FULL_ACCESS: addon.with_full_access,
|
||||||
ATTR_APPARMOR: addon.apparmor,
|
ATTR_APPARMOR: addon.apparmor,
|
||||||
ATTR_DEVICES: self._pretty_devices(addon),
|
ATTR_DEVICES: _pretty_devices(addon),
|
||||||
ATTR_ICON: addon.with_icon,
|
ATTR_ICON: addon.with_icon,
|
||||||
ATTR_LOGO: addon.with_logo,
|
ATTR_LOGO: addon.with_logo,
|
||||||
ATTR_CHANGELOG: addon.with_changelog,
|
ATTR_CHANGELOG: addon.with_changelog,
|
||||||
@ -161,13 +155,13 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_AUDIO: addon.with_audio,
|
ATTR_AUDIO: addon.with_audio,
|
||||||
ATTR_AUDIO_INPUT: addon.audio_input,
|
ATTR_AUDIO_INPUT: addon.audio_input,
|
||||||
ATTR_AUDIO_OUTPUT: addon.audio_output,
|
ATTR_AUDIO_OUTPUT: addon.audio_output,
|
||||||
ATTR_SERVICES: addon.services,
|
ATTR_SERVICES: _pretty_services(addon),
|
||||||
ATTR_DISCOVERY: addon.discovery,
|
ATTR_DISCOVERY: addon.discovery,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def options(self, request):
|
async def options(self, request):
|
||||||
"""Store user options for addon."""
|
"""Store user options for add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
addon_schema = SCHEMA_OPTIONS.extend({
|
addon_schema = SCHEMA_OPTIONS.extend({
|
||||||
@ -194,15 +188,8 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def security(self, request):
|
async def security(self, request):
|
||||||
"""Store security options for addon."""
|
"""Store security options for add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
# Have Access
|
|
||||||
# REMOVE: don't needed anymore
|
|
||||||
if addon.slug == request[REQUEST_FROM]:
|
|
||||||
_LOGGER.error("Can't self modify his security!")
|
|
||||||
raise APINotSupportedError()
|
|
||||||
|
|
||||||
body = await api_validate(SCHEMA_SECURITY, request)
|
body = await api_validate(SCHEMA_SECURITY, request)
|
||||||
|
|
||||||
if ATTR_PROTECTED in body:
|
if ATTR_PROTECTED in body:
|
||||||
@ -233,19 +220,19 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def install(self, request):
|
def install(self, request):
|
||||||
"""Install addon."""
|
"""Install add-on."""
|
||||||
addon = self._extract_addon(request, check_installed=False)
|
addon = self._extract_addon(request, check_installed=False)
|
||||||
return asyncio.shield(addon.install())
|
return asyncio.shield(addon.install())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def uninstall(self, request):
|
def uninstall(self, request):
|
||||||
"""Uninstall addon."""
|
"""Uninstall add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
return asyncio.shield(addon.uninstall())
|
return asyncio.shield(addon.uninstall())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def start(self, request):
|
def start(self, request):
|
||||||
"""Start addon."""
|
"""Start add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
# check options
|
# check options
|
||||||
@ -259,13 +246,13 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def stop(self, request):
|
def stop(self, request):
|
||||||
"""Stop addon."""
|
"""Stop add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
return asyncio.shield(addon.stop())
|
return asyncio.shield(addon.stop())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def update(self, request):
|
def update(self, request):
|
||||||
"""Update addon."""
|
"""Update add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
|
|
||||||
if addon.last_version == addon.version_installed:
|
if addon.last_version == addon.version_installed:
|
||||||
@ -275,13 +262,13 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restart(self, request):
|
def restart(self, request):
|
||||||
"""Restart addon."""
|
"""Restart add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
return asyncio.shield(addon.restart())
|
return asyncio.shield(addon.restart())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def rebuild(self, request):
|
def rebuild(self, request):
|
||||||
"""Rebuild local build addon."""
|
"""Rebuild local build add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
if not addon.need_build:
|
if not addon.need_build:
|
||||||
raise RuntimeError("Only local build addons are supported")
|
raise RuntimeError("Only local build addons are supported")
|
||||||
@ -290,13 +277,13 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request):
|
def logs(self, request):
|
||||||
"""Return logs from addon."""
|
"""Return logs from add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
return addon.logs()
|
return addon.logs()
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_PNG)
|
@api_process_raw(CONTENT_TYPE_PNG)
|
||||||
async def icon(self, request):
|
async def icon(self, request):
|
||||||
"""Return icon from addon."""
|
"""Return icon from add-on."""
|
||||||
addon = self._extract_addon(request, check_installed=False)
|
addon = self._extract_addon(request, check_installed=False)
|
||||||
if not addon.with_icon:
|
if not addon.with_icon:
|
||||||
raise RuntimeError("No icon found!")
|
raise RuntimeError("No icon found!")
|
||||||
@ -306,7 +293,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_PNG)
|
@api_process_raw(CONTENT_TYPE_PNG)
|
||||||
async def logo(self, request):
|
async def logo(self, request):
|
||||||
"""Return logo from addon."""
|
"""Return logo from add-on."""
|
||||||
addon = self._extract_addon(request, check_installed=False)
|
addon = self._extract_addon(request, check_installed=False)
|
||||||
if not addon.with_logo:
|
if not addon.with_logo:
|
||||||
raise RuntimeError("No logo found!")
|
raise RuntimeError("No logo found!")
|
||||||
@ -316,7 +303,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_TEXT)
|
@api_process_raw(CONTENT_TYPE_TEXT)
|
||||||
async def changelog(self, request):
|
async def changelog(self, request):
|
||||||
"""Return changelog from addon."""
|
"""Return changelog from add-on."""
|
||||||
addon = self._extract_addon(request, check_installed=False)
|
addon = self._extract_addon(request, check_installed=False)
|
||||||
if not addon.with_changelog:
|
if not addon.with_changelog:
|
||||||
raise RuntimeError("No changelog found!")
|
raise RuntimeError("No changelog found!")
|
||||||
@ -326,10 +313,26 @@ class APIAddons(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stdin(self, request):
|
async def stdin(self, request):
|
||||||
"""Write to stdin of addon."""
|
"""Write to stdin of add-on."""
|
||||||
addon = self._extract_addon(request)
|
addon = self._extract_addon(request)
|
||||||
if not addon.with_stdin:
|
if not addon.with_stdin:
|
||||||
raise RuntimeError("STDIN not supported by addon")
|
raise RuntimeError("STDIN not supported by add-on")
|
||||||
|
|
||||||
data = await request.read()
|
data = await request.read()
|
||||||
return await asyncio.shield(addon.write_stdin(data))
|
return await asyncio.shield(addon.write_stdin(data))
|
||||||
|
|
||||||
|
|
||||||
|
def _pretty_devices(addon):
|
||||||
|
"""Return a simplified device list."""
|
||||||
|
dev_list = addon.devices
|
||||||
|
if not dev_list:
|
||||||
|
return None
|
||||||
|
return [row.split(':')[0] for row in dev_list]
|
||||||
|
|
||||||
|
|
||||||
|
def _pretty_services(addon):
|
||||||
|
"""Return a simplified services role list."""
|
||||||
|
services = []
|
||||||
|
for name, access in addon.services_role.items():
|
||||||
|
services.append(f"{name}:{access}")
|
||||||
|
return services
|
||||||
|
@ -1,38 +1,48 @@
|
|||||||
"""Init file for HassIO network rest api."""
|
"""Init file for Hass.io network RESTful API."""
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_PROVIDER, ATTR_UUID, ATTR_COMPONENT, ATTR_PLATFORM, ATTR_CONFIG,
|
ATTR_ADDON, ATTR_UUID, ATTR_COMPONENT, ATTR_PLATFORM, ATTR_CONFIG,
|
||||||
ATTR_DISCOVERY, REQUEST_FROM)
|
ATTR_DISCOVERY, ATTR_SERVICE, REQUEST_FROM)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..exceptions import APIError, APIForbidden
|
||||||
|
from ..validate import SERVICE_ALL
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_DISCOVERY = vol.Schema({
|
SCHEMA_DISCOVERY = vol.Schema({
|
||||||
|
vol.Required(ATTR_SERVICE): SERVICE_ALL,
|
||||||
vol.Required(ATTR_COMPONENT): vol.Coerce(str),
|
vol.Required(ATTR_COMPONENT): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_PLATFORM): vol.Any(None, vol.Coerce(str)),
|
vol.Optional(ATTR_PLATFORM): vol.Maybe(vol.Coerce(str)),
|
||||||
vol.Optional(ATTR_CONFIG): vol.Any(None, dict),
|
vol.Optional(ATTR_CONFIG): vol.Maybe(dict),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class APIDiscovery(CoreSysAttributes):
|
class APIDiscovery(CoreSysAttributes):
|
||||||
"""Handle rest api for discovery functions."""
|
"""Handle RESTful API for discovery functions."""
|
||||||
|
|
||||||
def _extract_message(self, request):
|
def _extract_message(self, request):
|
||||||
"""Extract discovery message from URL."""
|
"""Extract discovery message from URL."""
|
||||||
message = self.sys_discovery.get(request.match_info.get('uuid'))
|
message = self.sys_discovery.get(request.match_info.get('uuid'))
|
||||||
if not message:
|
if not message:
|
||||||
raise RuntimeError("Discovery message not found")
|
raise APIError("Discovery message not found")
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
def _check_permission_ha(self, request):
|
||||||
|
"""Check permission for API call / Home Assistant."""
|
||||||
|
if request[REQUEST_FROM] != self.sys_homeassistant:
|
||||||
|
raise APIForbidden("Only HomeAssistant can use this API!")
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def list(self, request):
|
async def list(self, request):
|
||||||
"""Show register services."""
|
"""Show register services."""
|
||||||
|
self._check_permission_ha(request)
|
||||||
|
|
||||||
discovery = []
|
discovery = []
|
||||||
for message in self.sys_discovery.list_messages:
|
for message in self.sys_discovery.list_messages:
|
||||||
discovery.append({
|
discovery.append({
|
||||||
ATTR_PROVIDER: message.provider,
|
ATTR_ADDON: message.addon,
|
||||||
|
ATTR_SERVICE: message.service,
|
||||||
ATTR_UUID: message.uuid,
|
ATTR_UUID: message.uuid,
|
||||||
ATTR_COMPONENT: message.component,
|
ATTR_COMPONENT: message.component,
|
||||||
ATTR_PLATFORM: message.platform,
|
ATTR_PLATFORM: message.platform,
|
||||||
@ -45,8 +55,14 @@ class APIDiscovery(CoreSysAttributes):
|
|||||||
async def set_discovery(self, request):
|
async def set_discovery(self, request):
|
||||||
"""Write data into a discovery pipeline."""
|
"""Write data into a discovery pipeline."""
|
||||||
body = await api_validate(SCHEMA_DISCOVERY, request)
|
body = await api_validate(SCHEMA_DISCOVERY, request)
|
||||||
message = self.sys_discovery.send(
|
addon = request[REQUEST_FROM]
|
||||||
provider=request[REQUEST_FROM], **body)
|
|
||||||
|
# Access?
|
||||||
|
if body[ATTR_SERVICE] not in addon.discovery:
|
||||||
|
raise APIForbidden(f"Can't use discovery!")
|
||||||
|
|
||||||
|
# Process discovery message
|
||||||
|
message = self.sys_discovery.send(addon, **body)
|
||||||
|
|
||||||
return {ATTR_UUID: message.uuid}
|
return {ATTR_UUID: message.uuid}
|
||||||
|
|
||||||
@ -55,8 +71,12 @@ class APIDiscovery(CoreSysAttributes):
|
|||||||
"""Read data into a discovery message."""
|
"""Read data into a discovery message."""
|
||||||
message = self._extract_message(request)
|
message = self._extract_message(request)
|
||||||
|
|
||||||
|
# HomeAssistant?
|
||||||
|
self._check_permission_ha(request)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ATTR_PROVIDER: message.provider,
|
ATTR_ADDON: message.addon,
|
||||||
|
ATTR_SERVICE: message.service,
|
||||||
ATTR_UUID: message.uuid,
|
ATTR_UUID: message.uuid,
|
||||||
ATTR_COMPONENT: message.component,
|
ATTR_COMPONENT: message.component,
|
||||||
ATTR_PLATFORM: message.platform,
|
ATTR_PLATFORM: message.platform,
|
||||||
@ -67,6 +87,11 @@ class APIDiscovery(CoreSysAttributes):
|
|||||||
async def del_discovery(self, request):
|
async def del_discovery(self, request):
|
||||||
"""Delete data into a discovery message."""
|
"""Delete data into a discovery message."""
|
||||||
message = self._extract_message(request)
|
message = self._extract_message(request)
|
||||||
|
addon = request[REQUEST_FROM]
|
||||||
|
|
||||||
|
# Permission
|
||||||
|
if message.addon != addon.slug:
|
||||||
|
raise APIForbidden(f"Can't remove discovery message")
|
||||||
|
|
||||||
self.sys_discovery.remove(message)
|
self.sys_discovery.remove(message)
|
||||||
return True
|
return True
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO hardware rest api."""
|
"""Init file for Hass.io hardware RESTful API."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .utils import api_process
|
from .utils import api_process
|
||||||
@ -10,7 +10,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class APIHardware(CoreSysAttributes):
|
class APIHardware(CoreSysAttributes):
|
||||||
"""Handle rest api for hardware functions."""
|
"""Handle RESTful API for hardware functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request):
|
async def info(self, request):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for Hass.io hassos rest api."""
|
"""Init file for Hass.io HassOS RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -18,11 +18,11 @@ SCHEMA_VERSION = vol.Schema({
|
|||||||
|
|
||||||
|
|
||||||
class APIHassOS(CoreSysAttributes):
|
class APIHassOS(CoreSysAttributes):
|
||||||
"""Handle rest api for hassos functions."""
|
"""Handle RESTful API for HassOS functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request):
|
async def info(self, request):
|
||||||
"""Return hassos information."""
|
"""Return HassOS information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_hassos.version,
|
ATTR_VERSION: self.sys_hassos.version,
|
||||||
ATTR_VERSION_CLI: self.sys_hassos.version_cli,
|
ATTR_VERSION_CLI: self.sys_hassos.version_cli,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO homeassistant rest api."""
|
"""Init file for Hass.io Home Assistant RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -13,6 +13,7 @@ from ..const import (
|
|||||||
ATTR_REFRESH_TOKEN, CONTENT_TYPE_BINARY)
|
ATTR_REFRESH_TOKEN, CONTENT_TYPE_BINARY)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..validate import NETWORK_PORT, DOCKER_IMAGE
|
from ..validate import NETWORK_PORT, DOCKER_IMAGE
|
||||||
|
from ..exceptions import APIError
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ SCHEMA_VERSION = vol.Schema({
|
|||||||
|
|
||||||
|
|
||||||
class APIHomeAssistant(CoreSysAttributes):
|
class APIHomeAssistant(CoreSysAttributes):
|
||||||
"""Handle rest api for homeassistant functions."""
|
"""Handle RESTful API for Home Assistant functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request):
|
async def info(self, request):
|
||||||
@ -59,7 +60,7 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def options(self, request):
|
async def options(self, request):
|
||||||
"""Set homeassistant options."""
|
"""Set Home Assistant options."""
|
||||||
body = await api_validate(SCHEMA_OPTIONS, request)
|
body = await api_validate(SCHEMA_OPTIONS, request)
|
||||||
|
|
||||||
if ATTR_IMAGE in body and ATTR_LAST_VERSION in body:
|
if ATTR_IMAGE in body and ATTR_LAST_VERSION in body:
|
||||||
@ -94,7 +95,7 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_homeassistant.stats()
|
stats = await self.sys_homeassistant.stats()
|
||||||
if not stats:
|
if not stats:
|
||||||
raise RuntimeError("No stats available")
|
raise APIError("No stats available")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ATTR_CPU_PERCENT: stats.cpu_percent,
|
ATTR_CPU_PERCENT: stats.cpu_percent,
|
||||||
@ -108,7 +109,7 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def update(self, request):
|
async def update(self, request):
|
||||||
"""Update homeassistant."""
|
"""Update Home Assistant."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_homeassistant.last_version)
|
version = body.get(ATTR_VERSION, self.sys_homeassistant.last_version)
|
||||||
|
|
||||||
@ -116,29 +117,29 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def stop(self, request):
|
def stop(self, request):
|
||||||
"""Stop homeassistant."""
|
"""Stop Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.stop())
|
return asyncio.shield(self.sys_homeassistant.stop())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def start(self, request):
|
def start(self, request):
|
||||||
"""Start homeassistant."""
|
"""Start Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.start())
|
return asyncio.shield(self.sys_homeassistant.start())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def restart(self, request):
|
def restart(self, request):
|
||||||
"""Restart homeassistant."""
|
"""Restart Home Assistant."""
|
||||||
return asyncio.shield(self.sys_homeassistant.restart())
|
return asyncio.shield(self.sys_homeassistant.restart())
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request):
|
def logs(self, request):
|
||||||
"""Return homeassistant docker logs."""
|
"""Return Home Assistant Docker logs."""
|
||||||
return self.sys_homeassistant.logs()
|
return self.sys_homeassistant.logs()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def check(self, request):
|
async def check(self, request):
|
||||||
"""Check config of homeassistant."""
|
"""Check configuration of Home Assistant."""
|
||||||
result = await self.sys_homeassistant.check_config()
|
result = await self.sys_homeassistant.check_config()
|
||||||
if not result.valid:
|
if not result.valid:
|
||||||
raise RuntimeError(result.log)
|
raise APIError(result.log)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO host rest api."""
|
"""Init file for Hass.io host RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ SCHEMA_OPTIONS = vol.Schema({
|
|||||||
|
|
||||||
|
|
||||||
class APIHost(CoreSysAttributes):
|
class APIHost(CoreSysAttributes):
|
||||||
"""Handle rest api for host functions."""
|
"""Handle RESTful API for host functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request):
|
async def info(self, request):
|
||||||
|
File diff suppressed because one or more lines are too long
Binary file not shown.
2
hassio/api/panel/chunk.457ac71b0904d7243237.js
Normal file
2
hassio/api/panel/chunk.457ac71b0904d7243237.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.457ac71b0904d7243237.js.gz
Normal file
BIN
hassio/api/panel/chunk.457ac71b0904d7243237.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.457ac71b0904d7243237.js.map
Normal file
1
hassio/api/panel/chunk.457ac71b0904d7243237.js.map
Normal file
File diff suppressed because one or more lines are too long
2
hassio/api/panel/chunk.57f5b43a82b988080555.js
Normal file
2
hassio/api/panel/chunk.57f5b43a82b988080555.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.57f5b43a82b988080555.js.gz
Normal file
BIN
hassio/api/panel/chunk.57f5b43a82b988080555.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.57f5b43a82b988080555.js.map
Normal file
1
hassio/api/panel/chunk.57f5b43a82b988080555.js.map
Normal file
File diff suppressed because one or more lines are too long
3
hassio/api/panel/chunk.72a6da063fe4cb6308e8.js
Normal file
3
hassio/api/panel/chunk.72a6da063fe4cb6308e8.js
Normal file
File diff suppressed because one or more lines are too long
652
hassio/api/panel/chunk.72a6da063fe4cb6308e8.js.LICENSE
Normal file
652
hassio/api/panel/chunk.72a6da063fe4cb6308e8.js.LICENSE
Normal file
@ -0,0 +1,652 @@
|
|||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
BIN
hassio/api/panel/chunk.72a6da063fe4cb6308e8.js.gz
Normal file
BIN
hassio/api/panel/chunk.72a6da063fe4cb6308e8.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.72a6da063fe4cb6308e8.js.map
Normal file
1
hassio/api/panel/chunk.72a6da063fe4cb6308e8.js.map
Normal file
File diff suppressed because one or more lines are too long
2
hassio/api/panel/chunk.7ee37c2565bcf2d88182.js
Normal file
2
hassio/api/panel/chunk.7ee37c2565bcf2d88182.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
(window.webpackJsonp=window.webpackJsonp||[]).push([[4],{102:function(n,r,t){"use strict";t.r(r),t.d(r,"marked",function(){return a}),t.d(r,"filterXSS",function(){return c});var e=t(91),i=t.n(e),o=t(93),u=t.n(o),a=i.a,c=u.a}}]);
|
||||||
|
//# sourceMappingURL=chunk.7ee37c2565bcf2d88182.js.map
|
BIN
hassio/api/panel/chunk.7ee37c2565bcf2d88182.js.gz
Normal file
BIN
hassio/api/panel/chunk.7ee37c2565bcf2d88182.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.7ee37c2565bcf2d88182.js.map
Normal file
1
hassio/api/panel/chunk.7ee37c2565bcf2d88182.js.map
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["webpack:///../src/resources/load_markdown.js"],"names":["__webpack_require__","r","__webpack_exports__","d","marked","filterXSS","marked__WEBPACK_IMPORTED_MODULE_0__","marked__WEBPACK_IMPORTED_MODULE_0___default","n","xss__WEBPACK_IMPORTED_MODULE_1__","xss__WEBPACK_IMPORTED_MODULE_1___default","a"],"mappings":"0FAAAA,EAAAC,EAAAC,GAAAF,EAAAG,EAAAD,EAAA,2BAAAE,IAAAJ,EAAAG,EAAAD,EAAA,8BAAAG,IAAA,IAAAC,EAAAN,EAAA,IAAAO,EAAAP,EAAAQ,EAAAF,GAAAG,EAAAT,EAAA,IAAAU,EAAAV,EAAAQ,EAAAC,GAGaL,EAASG,EAAAI,EACTN,EAAYK,EAAAC","file":"chunk.7ee37c2565bcf2d88182.js","sourcesContent":["import marked_ from 'marked';\nimport filterXSS_ from 'xss';\n\nexport const marked = marked_;\nexport const filterXSS = filterXSS_;\n"],"sourceRoot":""}
|
File diff suppressed because one or more lines are too long
@ -1,419 +0,0 @@
|
|||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
Binary file not shown.
3
hassio/api/panel/chunk.a8fa5591357cce978816.js
Normal file
3
hassio/api/panel/chunk.a8fa5591357cce978816.js
Normal file
File diff suppressed because one or more lines are too long
@ -138,89 +138,101 @@ Code distributed by Google as part of the polymer project is also
|
|||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@license
|
@license
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
BIN
hassio/api/panel/chunk.a8fa5591357cce978816.js.gz
Normal file
BIN
hassio/api/panel/chunk.a8fa5591357cce978816.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.a8fa5591357cce978816.js.map
Normal file
1
hassio/api/panel/chunk.a8fa5591357cce978816.js.map
Normal file
File diff suppressed because one or more lines are too long
2
hassio/api/panel/chunk.ad9001ac29bd3acbb520.js
Normal file
2
hassio/api/panel/chunk.ad9001ac29bd3acbb520.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.ad9001ac29bd3acbb520.js.gz
Normal file
BIN
hassio/api/panel/chunk.ad9001ac29bd3acbb520.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.ad9001ac29bd3acbb520.js.map
Normal file
1
hassio/api/panel/chunk.ad9001ac29bd3acbb520.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
|||||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[5],{104:function(n,r,t){"use strict";t.r(r),t.d(r,"marked",function(){return a}),t.d(r,"filterXSS",function(){return c});var e=t(99),i=t.n(e),o=t(97),u=t.n(o),a=i.a,c=u.a}}]);
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +1,2 @@
|
|||||||
!function(e){function n(n){for(var t,o,i=n[0],u=n[1],a=0,l=[];a<i.length;a++)o=i[a],r[o]&&l.push(r[o][0]),r[o]=0;for(t in u)Object.prototype.hasOwnProperty.call(u,t)&&(e[t]=u[t]);for(f&&f(n);l.length;)l.shift()()}var t={},r={6:0};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.e=function(e){var n=[],t=r[e];if(0!==t)if(t)n.push(t[2]);else{var i=new Promise(function(n,o){t=r[e]=[n,o]});n.push(t[2]=i);var u,a=document.getElementsByTagName("head")[0],f=document.createElement("script");f.charset="utf-8",f.timeout=120,o.nc&&f.setAttribute("nonce",o.nc),f.src=function(e){return o.p+"chunk."+{0:"f3880aa331d3ef2ddf32",1:"a8e86d80be46b3b6e16d",2:"0ef4ef1053fe3d5107b5",3:"ff92199b0d422767d108",4:"c77b56beea1d4547ff5f",5:"c93f37c558ff32991708"}[e]+".js"}(e),u=function(n){f.onerror=f.onload=null,clearTimeout(l);var t=r[e];if(0!==t){if(t){var o=n&&("load"===n.type?"missing":n.type),i=n&&n.target&&n.target.src,u=new Error("Loading chunk "+e+" failed.\n("+o+": "+i+")");u.type=o,u.request=i,t[1](u)}r[e]=void 0}};var l=setTimeout(function(){u({type:"timeout",target:f})},12e4);f.onerror=f.onload=u,a.appendChild(f)}return Promise.all(n)},o.m=e,o.c=t,o.d=function(e,n,t){o.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,n){if(1&n&&(e=o(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(o.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)o.d(t,r,function(n){return e[n]}.bind(null,r));return t},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,"a",n),n},o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.p="/api/hassio/app/",o.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],u=i.push.bind(i);i.push=n,i=i.slice();for(var a=0;a<i.length;a++)n(i[a]);var f=u;o(o.s=0)}([function(e,n,t){window.loadES5Adapter().then(function(){Promise.all([t.e(0),t.e(3)]).then(t.bind(null,1)),Promise.all([t.e(0),t.e(1),t.e(2)]).then(t.bind(null,2))})}]);
|
!function(e){function n(n){for(var t,o,a=n[0],i=n[1],u=0,l=[];u<a.length;u++)o=a[u],r[o]&&l.push(r[o][0]),r[o]=0;for(t in i)Object.prototype.hasOwnProperty.call(i,t)&&(e[t]=i[t]);for(c&&c(n);l.length;)l.shift()()}var t={},r={1:0};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.e=function(e){var n=[],t=r[e];if(0!==t)if(t)n.push(t[2]);else{var a=new Promise(function(n,o){t=r[e]=[n,o]});n.push(t[2]=a);var i,u=document.getElementsByTagName("head")[0],c=document.createElement("script");c.charset="utf-8",c.timeout=120,o.nc&&c.setAttribute("nonce",o.nc),c.src=function(e){return o.p+"chunk."+{0:"a8fa5591357cce978816",2:"457ac71b0904d7243237",3:"57f5b43a82b988080555",4:"7ee37c2565bcf2d88182",5:"72a6da063fe4cb6308e8",6:"ad9001ac29bd3acbb520"}[e]+".js"}(e),i=function(n){c.onerror=c.onload=null,clearTimeout(l);var t=r[e];if(0!==t){if(t){var o=n&&("load"===n.type?"missing":n.type),a=n&&n.target&&n.target.src,i=new Error("Loading chunk "+e+" failed.\n("+o+": "+a+")");i.type=o,i.request=a,t[1](i)}r[e]=void 0}};var l=setTimeout(function(){i({type:"timeout",target:c})},12e4);c.onerror=c.onload=i,u.appendChild(c)}return Promise.all(n)},o.m=e,o.c=t,o.d=function(e,n,t){o.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,n){if(1&n&&(e=o(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(o.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)o.d(t,r,function(n){return e[n]}.bind(null,r));return t},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,"a",n),n},o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.p="/api/hassio/app/",o.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],i=a.push.bind(a);a.push=n,a=a.slice();for(var u=0;u<a.length;u++)n(a[u]);var c=i;o(o.s=0)}([function(e,n,t){window.loadES5Adapter().then(function(){Promise.all([t.e(0),t.e(2)]).then(t.bind(null,2)),Promise.all([t.e(0),t.e(5),t.e(3)]).then(t.bind(null,1))})}]);
|
||||||
|
//# sourceMappingURL=entrypoint.js.map
|
Binary file not shown.
1
hassio/api/panel/entrypoint.js.map
Normal file
1
hassio/api/panel/entrypoint.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -7,7 +7,7 @@ import aiohttp
|
|||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
from aiohttp.web_exceptions import (
|
from aiohttp.web_exceptions import (
|
||||||
HTTPBadGateway, HTTPInternalServerError, HTTPUnauthorized)
|
HTTPBadGateway, HTTPInternalServerError, HTTPUnauthorized)
|
||||||
from aiohttp.hdrs import CONTENT_TYPE
|
from aiohttp.hdrs import CONTENT_TYPE, AUTHORIZATION
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
|
||||||
from ..const import HEADER_HA_ACCESS
|
from ..const import HEADER_HA_ACCESS
|
||||||
@ -18,17 +18,17 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class APIProxy(CoreSysAttributes):
|
class APIProxy(CoreSysAttributes):
|
||||||
"""API Proxy for Home-Assistant."""
|
"""API Proxy for Home Assistant."""
|
||||||
|
|
||||||
def _check_access(self, request):
|
def _check_access(self, request):
|
||||||
"""Check the Hass.io token."""
|
"""Check the Hass.io token."""
|
||||||
|
if AUTHORIZATION in request.headers:
|
||||||
|
bearer = request.headers[AUTHORIZATION]
|
||||||
|
hassio_token = bearer.split(' ')[-1]
|
||||||
|
else:
|
||||||
hassio_token = request.headers.get(HEADER_HA_ACCESS)
|
hassio_token = request.headers.get(HEADER_HA_ACCESS)
|
||||||
|
|
||||||
addon = self.sys_addons.from_token(hassio_token)
|
addon = self.sys_addons.from_token(hassio_token)
|
||||||
|
|
||||||
# REMOVE 132
|
|
||||||
if not addon:
|
|
||||||
addon = self.sys_addons.from_uuid(hassio_token)
|
|
||||||
|
|
||||||
if not addon:
|
if not addon:
|
||||||
_LOGGER.warning("Unknown Home Assistant API access!")
|
_LOGGER.warning("Unknown Home Assistant API access!")
|
||||||
elif not addon.access_homeassistant_api:
|
elif not addon.access_homeassistant_api:
|
||||||
@ -41,7 +41,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def _api_client(self, request, path, timeout=300):
|
async def _api_client(self, request, path, timeout=300):
|
||||||
"""Return a client request with proxy origin for Home-Assistant."""
|
"""Return a client request with proxy origin for Home Assistant."""
|
||||||
try:
|
try:
|
||||||
# read data
|
# read data
|
||||||
with async_timeout.timeout(30):
|
with async_timeout.timeout(30):
|
||||||
@ -76,7 +76,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
"""Proxy HomeAssistant EventStream Requests."""
|
"""Proxy HomeAssistant EventStream Requests."""
|
||||||
self._check_access(request)
|
self._check_access(request)
|
||||||
|
|
||||||
_LOGGER.info("Home-Assistant EventStream start")
|
_LOGGER.info("Home Assistant EventStream start")
|
||||||
async with self._api_client(request, 'stream', timeout=None) as client:
|
async with self._api_client(request, 'stream', timeout=None) as client:
|
||||||
response = web.StreamResponse()
|
response = web.StreamResponse()
|
||||||
response.content_type = request.headers.get(CONTENT_TYPE)
|
response.content_type = request.headers.get(CONTENT_TYPE)
|
||||||
@ -93,7 +93,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
|
|
||||||
finally:
|
finally:
|
||||||
client.close()
|
client.close()
|
||||||
_LOGGER.info("Home-Assistant EventStream close")
|
_LOGGER.info("Home Assistant EventStream close")
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@ -112,14 +112,14 @@ class APIProxy(CoreSysAttributes):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def _websocket_client(self):
|
async def _websocket_client(self):
|
||||||
"""Initialize a websocket api connection."""
|
"""Initialize a WebSocket API connection."""
|
||||||
url = f"{self.sys_homeassistant.api_url}/api/websocket"
|
url = f"{self.sys_homeassistant.api_url}/api/websocket"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = await self.sys_websession_ssl.ws_connect(
|
client = await self.sys_websession_ssl.ws_connect(
|
||||||
url, heartbeat=60, verify_ssl=False)
|
url, heartbeat=60, verify_ssl=False)
|
||||||
|
|
||||||
# handle authentication
|
# Handle authentication
|
||||||
data = await client.receive_json()
|
data = await client.receive_json()
|
||||||
|
|
||||||
if data.get('type') == 'auth_ok':
|
if data.get('type') == 'auth_ok':
|
||||||
@ -128,7 +128,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
if data.get('type') != 'auth_required':
|
if data.get('type') != 'auth_required':
|
||||||
# Invalid protocol
|
# Invalid protocol
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'Got unexpected response from HA websocket: %s', data)
|
"Got unexpected response from HA WebSocket: %s", data)
|
||||||
raise HTTPBadGateway()
|
raise HTTPBadGateway()
|
||||||
|
|
||||||
if self.sys_homeassistant.refresh_token:
|
if self.sys_homeassistant.refresh_token:
|
||||||
@ -157,15 +157,15 @@ class APIProxy(CoreSysAttributes):
|
|||||||
raise HomeAssistantAuthError()
|
raise HomeAssistantAuthError()
|
||||||
|
|
||||||
except (RuntimeError, ValueError) as err:
|
except (RuntimeError, ValueError) as err:
|
||||||
_LOGGER.error("Client error on websocket API %s.", err)
|
_LOGGER.error("Client error on WebSocket API %s.", err)
|
||||||
except HomeAssistantAuthError as err:
|
except HomeAssistantAuthError as err:
|
||||||
_LOGGER.error("Failed authentication to HomeAssistant websocket")
|
_LOGGER.error("Failed authentication to Home Assistant WebSocket")
|
||||||
|
|
||||||
raise HTTPBadGateway()
|
raise HTTPBadGateway()
|
||||||
|
|
||||||
async def websocket(self, request):
|
async def websocket(self, request):
|
||||||
"""Initialize a websocket api connection."""
|
"""Initialize a WebSocket API connection."""
|
||||||
_LOGGER.info("Home-Assistant Websocket API request initialze")
|
_LOGGER.info("Home Assistant WebSocket API request initialize")
|
||||||
|
|
||||||
# init server
|
# init server
|
||||||
server = web.WebSocketResponse(heartbeat=60)
|
server = web.WebSocketResponse(heartbeat=60)
|
||||||
@ -184,19 +184,15 @@ class APIProxy(CoreSysAttributes):
|
|||||||
response.get('access_token'))
|
response.get('access_token'))
|
||||||
addon = self.sys_addons.from_token(hassio_token)
|
addon = self.sys_addons.from_token(hassio_token)
|
||||||
|
|
||||||
# REMOVE 132
|
|
||||||
if not addon:
|
|
||||||
addon = self.sys_addons.from_uuid(hassio_token)
|
|
||||||
|
|
||||||
if not addon or not addon.access_homeassistant_api:
|
if not addon or not addon.access_homeassistant_api:
|
||||||
_LOGGER.warning("Unauthorized websocket access!")
|
_LOGGER.warning("Unauthorized WebSocket access!")
|
||||||
await server.send_json({
|
await server.send_json({
|
||||||
'type': 'auth_invalid',
|
'type': 'auth_invalid',
|
||||||
'message': 'Invalid access',
|
'message': 'Invalid access',
|
||||||
})
|
})
|
||||||
return server
|
return server
|
||||||
|
|
||||||
_LOGGER.info("Websocket access from %s", addon.slug)
|
_LOGGER.info("WebSocket access from %s", addon.slug)
|
||||||
|
|
||||||
await server.send_json({
|
await server.send_json({
|
||||||
'type': 'auth_ok',
|
'type': 'auth_ok',
|
||||||
@ -209,7 +205,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
# init connection to hass
|
# init connection to hass
|
||||||
client = await self._websocket_client()
|
client = await self._websocket_client()
|
||||||
|
|
||||||
_LOGGER.info("Home-Assistant Websocket API request running")
|
_LOGGER.info("Home Assistant WebSocket API request running")
|
||||||
try:
|
try:
|
||||||
client_read = None
|
client_read = None
|
||||||
server_read = None
|
server_read = None
|
||||||
@ -243,7 +239,7 @@ class APIProxy(CoreSysAttributes):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
except RuntimeError as err:
|
except RuntimeError as err:
|
||||||
_LOGGER.info("Home-Assistant Websocket API error: %s", err)
|
_LOGGER.info("Home Assistant WebSocket API error: %s", err)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if client_read:
|
if client_read:
|
||||||
@ -255,5 +251,5 @@ class APIProxy(CoreSysAttributes):
|
|||||||
await client.close()
|
await client.close()
|
||||||
await server.close()
|
await server.close()
|
||||||
|
|
||||||
_LOGGER.info("Home-Assistant Websocket API connection is closed")
|
_LOGGER.info("Home Assistant WebSocket API connection is closed")
|
||||||
return server
|
return server
|
||||||
|
@ -12,22 +12,30 @@ from ..coresys import CoreSysAttributes
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Block Anytime
|
||||||
|
BLACKLIST = re.compile(
|
||||||
|
r"^(?:"
|
||||||
|
r"|/homeassistant/api/hassio/.*"
|
||||||
|
r")$"
|
||||||
|
)
|
||||||
|
|
||||||
# Free to call or have own security concepts
|
# Free to call or have own security concepts
|
||||||
NO_SECURITY_CHECK = re.compile(
|
NO_SECURITY_CHECK = re.compile(
|
||||||
r"^(?:"
|
r"^(?:"
|
||||||
r"|/homeassistant/api/.*"
|
r"|/homeassistant/api/.*"
|
||||||
r"|/homeassistant/websocket"
|
r"|/homeassistant/websocket"
|
||||||
r"|/supervisor/ping"
|
r"|/supervisor/ping"
|
||||||
r"|/services.*"
|
|
||||||
r")$"
|
r")$"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Can called by every add-on
|
# Can called by every add-on
|
||||||
ADDONS_API_BYPASS = re.compile(
|
ADDONS_API_BYPASS = re.compile(
|
||||||
r"^(?:"
|
r"^(?:"
|
||||||
r"|/homeassistant/info"
|
r"|/addons/self/(?!security)[^/]+"
|
||||||
r"|/supervisor/info"
|
r"|/version"
|
||||||
r"|/addons(?:/self/(?!security)[^/]+)?"
|
r"|/services.*"
|
||||||
|
r"|/discovery.*"
|
||||||
r")$"
|
r")$"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,6 +44,7 @@ ADDONS_ROLE_ACCESS = {
|
|||||||
ROLE_DEFAULT: re.compile(
|
ROLE_DEFAULT: re.compile(
|
||||||
r"^(?:"
|
r"^(?:"
|
||||||
r"|/[^/]+/info"
|
r"|/[^/]+/info"
|
||||||
|
r"|/addons"
|
||||||
r")$"
|
r")$"
|
||||||
),
|
),
|
||||||
ROLE_HOMEASSISTANT: re.compile(
|
ROLE_HOMEASSISTANT: re.compile(
|
||||||
@ -50,13 +59,12 @@ ADDONS_ROLE_ACCESS = {
|
|||||||
r"|/hardware/.+"
|
r"|/hardware/.+"
|
||||||
r"|/hassos/.+"
|
r"|/hassos/.+"
|
||||||
r"|/supervisor/.+"
|
r"|/supervisor/.+"
|
||||||
r"|/addons/.+/(?!security|options).+"
|
r"|/addons/[^/]+/(?!security).+"
|
||||||
r"|/addons(?:/self/(?!security).+)"
|
|
||||||
r"|/snapshots.*"
|
r"|/snapshots.*"
|
||||||
r")$"
|
r")$"
|
||||||
),
|
),
|
||||||
ROLE_ADMIN: re.compile(
|
ROLE_ADMIN: re.compile(
|
||||||
r".+"
|
r".*"
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +82,11 @@ class SecurityMiddleware(CoreSysAttributes):
|
|||||||
request_from = None
|
request_from = None
|
||||||
hassio_token = request.headers.get(HEADER_TOKEN)
|
hassio_token = request.headers.get(HEADER_TOKEN)
|
||||||
|
|
||||||
|
# Blacklist
|
||||||
|
if BLACKLIST.match(request.path):
|
||||||
|
_LOGGER.warning("%s is blacklisted!", request.path)
|
||||||
|
raise HTTPForbidden()
|
||||||
|
|
||||||
# Ignore security check
|
# Ignore security check
|
||||||
if NO_SECURITY_CHECK.match(request.path):
|
if NO_SECURITY_CHECK.match(request.path):
|
||||||
_LOGGER.debug("Passthrough %s", request.path)
|
_LOGGER.debug("Passthrough %s", request.path)
|
||||||
@ -88,38 +101,34 @@ class SecurityMiddleware(CoreSysAttributes):
|
|||||||
# UUID check need removed with 131
|
# UUID check need removed with 131
|
||||||
if hassio_token in (self.sys_homeassistant.uuid,
|
if hassio_token in (self.sys_homeassistant.uuid,
|
||||||
self.sys_homeassistant.hassio_token):
|
self.sys_homeassistant.hassio_token):
|
||||||
_LOGGER.debug("%s access from Home-Assistant", request.path)
|
_LOGGER.debug("%s access from Home Assistant", request.path)
|
||||||
request_from = 'homeassistant'
|
request_from = self.sys_homeassistant
|
||||||
|
|
||||||
# Host
|
# Host
|
||||||
if hassio_token == self.sys_machine_id:
|
if hassio_token == self.sys_machine_id:
|
||||||
_LOGGER.debug("%s access from Host", request.path)
|
_LOGGER.debug("%s access from Host", request.path)
|
||||||
request_from = 'host'
|
request_from = self.sys_host
|
||||||
|
|
||||||
# Add-on
|
# Add-on
|
||||||
addon = None
|
addon = None
|
||||||
if hassio_token and not request_from:
|
if hassio_token and not request_from:
|
||||||
addon = self.sys_addons.from_token(hassio_token)
|
addon = self.sys_addons.from_token(hassio_token)
|
||||||
# REMOVE 132
|
|
||||||
if not addon:
|
|
||||||
addon = self.sys_addons.from_uuid(hassio_token)
|
|
||||||
|
|
||||||
# Check Add-on API access
|
# Check Add-on API access
|
||||||
if addon and ADDONS_API_BYPASS.match(request.path):
|
if addon and ADDONS_API_BYPASS.match(request.path):
|
||||||
_LOGGER.debug("Passthrough %s from %s", request.path, addon.slug)
|
_LOGGER.debug("Passthrough %s from %s", request.path, addon.slug)
|
||||||
request_from = addon.slug
|
request_from = addon
|
||||||
elif addon and addon.access_hassio_api:
|
elif addon and addon.access_hassio_api:
|
||||||
# Check Role
|
# Check Role
|
||||||
if ADDONS_ROLE_ACCESS[addon.hassio_role].match(request.path):
|
if ADDONS_ROLE_ACCESS[addon.hassio_role].match(request.path):
|
||||||
_LOGGER.info("%s access from %s", request.path, addon.slug)
|
_LOGGER.info("%s access from %s", request.path, addon.slug)
|
||||||
request_from = addon.slug
|
request_from = addon
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning("%s no role for %s", request.path, addon.slug)
|
_LOGGER.warning("%s no role for %s", request.path, addon.slug)
|
||||||
request_from = addon.slug # REMOVE: 132
|
|
||||||
|
|
||||||
if request_from:
|
if request_from:
|
||||||
request[REQUEST_FROM] = request_from
|
request[REQUEST_FROM] = request_from
|
||||||
return await handler(request)
|
return await handler(request)
|
||||||
|
|
||||||
_LOGGER.warning("Invalid token for access %s", request.path)
|
_LOGGER.error("Invalid token for access %s", request.path)
|
||||||
raise HTTPForbidden()
|
raise HTTPForbidden()
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
"""Init file for HassIO network rest api."""
|
"""Init file for Hass.io network RESTful API."""
|
||||||
|
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_AVAILABLE, ATTR_PROVIDER, ATTR_SLUG, ATTR_SERVICES, REQUEST_FROM)
|
ATTR_AVAILABLE, ATTR_PROVIDERS, ATTR_SLUG, ATTR_SERVICES, REQUEST_FROM,
|
||||||
|
PROVIDE_SERVICE)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..exceptions import APIError, APIForbidden
|
||||||
|
|
||||||
|
|
||||||
class APIServices(CoreSysAttributes):
|
class APIServices(CoreSysAttributes):
|
||||||
"""Handle rest api for services functions."""
|
"""Handle RESTful API for services functions."""
|
||||||
|
|
||||||
def _extract_service(self, request):
|
def _extract_service(self, request):
|
||||||
"""Return service, throw an exception if it doesn't exist."""
|
"""Return service, throw an exception if it doesn't exist."""
|
||||||
service = self.sys_services.get(request.match_info.get('service'))
|
service = self.sys_services.get(request.match_info.get('service'))
|
||||||
if not service:
|
if not service:
|
||||||
raise RuntimeError("Service does not exist")
|
raise APIError("Service does not exist")
|
||||||
|
|
||||||
return service
|
return service
|
||||||
|
|
||||||
@ -25,7 +27,7 @@ class APIServices(CoreSysAttributes):
|
|||||||
services.append({
|
services.append({
|
||||||
ATTR_SLUG: service.slug,
|
ATTR_SLUG: service.slug,
|
||||||
ATTR_AVAILABLE: service.enabled,
|
ATTR_AVAILABLE: service.enabled,
|
||||||
ATTR_PROVIDER: service.provider,
|
ATTR_PROVIDERS: service.providers,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {ATTR_SERVICES: services}
|
return {ATTR_SERVICES: services}
|
||||||
@ -35,21 +37,39 @@ class APIServices(CoreSysAttributes):
|
|||||||
"""Write data into a service."""
|
"""Write data into a service."""
|
||||||
service = self._extract_service(request)
|
service = self._extract_service(request)
|
||||||
body = await api_validate(service.schema, request)
|
body = await api_validate(service.schema, request)
|
||||||
|
addon = request[REQUEST_FROM]
|
||||||
|
|
||||||
return service.set_service_data(request[REQUEST_FROM], body)
|
_check_access(request, service.slug)
|
||||||
|
service.set_service_data(addon, body)
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def get_service(self, request):
|
async def get_service(self, request):
|
||||||
"""Read data into a service."""
|
"""Read data into a service."""
|
||||||
service = self._extract_service(request)
|
service = self._extract_service(request)
|
||||||
|
|
||||||
return {
|
# Access
|
||||||
ATTR_AVAILABLE: service.enabled,
|
_check_access(request, service.slug)
|
||||||
service.slug: service.get_service_data(),
|
|
||||||
}
|
if not service.enabled:
|
||||||
|
raise APIError("Service not enabled")
|
||||||
|
return service.get_service_data()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def del_service(self, request):
|
async def del_service(self, request):
|
||||||
"""Delete data into a service."""
|
"""Delete data into a service."""
|
||||||
service = self._extract_service(request)
|
service = self._extract_service(request)
|
||||||
return service.del_service_data(request[REQUEST_FROM])
|
addon = request[REQUEST_FROM]
|
||||||
|
|
||||||
|
# Access
|
||||||
|
_check_access(request, service.slug, True)
|
||||||
|
service.del_service_data(addon)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_access(request, service, provide=False):
|
||||||
|
"""Raise error if the rights are wrong."""
|
||||||
|
addon = request[REQUEST_FROM]
|
||||||
|
if not addon.services_role.get(service):
|
||||||
|
raise APIForbidden(f"No access to {service} service!")
|
||||||
|
|
||||||
|
if provide and addon.services_role.get(service) != PROVIDE_SERVICE:
|
||||||
|
raise APIForbidden(f"No access to write {service} service!")
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO snapshot rest api."""
|
"""Init file for Hass.io snapshot RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -14,6 +14,7 @@ from ..const import (
|
|||||||
ATTR_HOMEASSISTANT, ATTR_VERSION, ATTR_SIZE, ATTR_FOLDERS, ATTR_TYPE,
|
ATTR_HOMEASSISTANT, ATTR_VERSION, ATTR_SIZE, ATTR_FOLDERS, ATTR_TYPE,
|
||||||
ATTR_SNAPSHOTS, ATTR_PASSWORD, ATTR_PROTECTED, CONTENT_TYPE_TAR)
|
ATTR_SNAPSHOTS, ATTR_PASSWORD, ATTR_PROTECTED, CONTENT_TYPE_TAR)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..exceptions import APIError
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -46,13 +47,13 @@ SCHEMA_SNAPSHOT_PARTIAL = SCHEMA_SNAPSHOT_FULL.extend({
|
|||||||
|
|
||||||
|
|
||||||
class APISnapshots(CoreSysAttributes):
|
class APISnapshots(CoreSysAttributes):
|
||||||
"""Handle rest api for snapshot functions."""
|
"""Handle RESTful API for snapshot functions."""
|
||||||
|
|
||||||
def _extract_snapshot(self, request):
|
def _extract_snapshot(self, request):
|
||||||
"""Return snapshot, throw an exception if it doesn't exist."""
|
"""Return snapshot, throw an exception if it doesn't exist."""
|
||||||
snapshot = self.sys_snapshots.get(request.match_info.get('snapshot'))
|
snapshot = self.sys_snapshots.get(request.match_info.get('snapshot'))
|
||||||
if not snapshot:
|
if not snapshot:
|
||||||
raise RuntimeError("Snapshot does not exist")
|
raise APIError("Snapshot does not exist")
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO supervisor rest api."""
|
"""Init file for Hass.io Supervisor RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -13,7 +13,9 @@ from ..const import (
|
|||||||
ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX, ATTR_BLK_READ,
|
ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX, ATTR_BLK_READ,
|
||||||
ATTR_BLK_WRITE, CONTENT_TYPE_BINARY, ATTR_ICON)
|
ATTR_BLK_WRITE, CONTENT_TYPE_BINARY, ATTR_ICON)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..validate import validate_timezone, WAIT_BOOT, REPOSITORIES, CHANNELS
|
from ..validate import WAIT_BOOT, REPOSITORIES, CHANNELS
|
||||||
|
from ..exceptions import APIError
|
||||||
|
from ..utils.validate import validate_timezone
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -30,11 +32,11 @@ SCHEMA_VERSION = vol.Schema({
|
|||||||
|
|
||||||
|
|
||||||
class APISupervisor(CoreSysAttributes):
|
class APISupervisor(CoreSysAttributes):
|
||||||
"""Handle rest api for supervisor functions."""
|
"""Handle RESTful API for Supervisor functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def ping(self, request):
|
async def ping(self, request):
|
||||||
"""Return ok for signal that the api is ready."""
|
"""Return ok for signal that the API is ready."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
@ -68,7 +70,7 @@ class APISupervisor(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def options(self, request):
|
async def options(self, request):
|
||||||
"""Set supervisor options."""
|
"""Set Supervisor options."""
|
||||||
body = await api_validate(SCHEMA_OPTIONS, request)
|
body = await api_validate(SCHEMA_OPTIONS, request)
|
||||||
|
|
||||||
if ATTR_CHANNEL in body:
|
if ATTR_CHANNEL in body:
|
||||||
@ -93,7 +95,7 @@ class APISupervisor(CoreSysAttributes):
|
|||||||
"""Return resource information."""
|
"""Return resource information."""
|
||||||
stats = await self.sys_supervisor.stats()
|
stats = await self.sys_supervisor.stats()
|
||||||
if not stats:
|
if not stats:
|
||||||
raise RuntimeError("No stats available")
|
raise APIError("No stats available")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ATTR_CPU_PERCENT: stats.cpu_percent,
|
ATTR_CPU_PERCENT: stats.cpu_percent,
|
||||||
@ -107,19 +109,19 @@ class APISupervisor(CoreSysAttributes):
|
|||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def update(self, request):
|
async def update(self, request):
|
||||||
"""Update supervisor OS."""
|
"""Update Supervisor OS."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_updater.version_hassio)
|
version = body.get(ATTR_VERSION, self.sys_updater.version_hassio)
|
||||||
|
|
||||||
if version == self.sys_supervisor.version:
|
if version == self.sys_supervisor.version:
|
||||||
raise RuntimeError("Version {} is already in use".format(version))
|
raise APIError("Version {} is already in use".format(version))
|
||||||
|
|
||||||
return await asyncio.shield(
|
return await asyncio.shield(
|
||||||
self.sys_supervisor.update(version))
|
self.sys_supervisor.update(version))
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def reload(self, request):
|
async def reload(self, request):
|
||||||
"""Reload addons, config etc."""
|
"""Reload add-ons, configuration, etc."""
|
||||||
tasks = [
|
tasks = [
|
||||||
self.sys_updater.reload(),
|
self.sys_updater.reload(),
|
||||||
]
|
]
|
||||||
@ -128,11 +130,11 @@ class APISupervisor(CoreSysAttributes):
|
|||||||
|
|
||||||
for result in results:
|
for result in results:
|
||||||
if result.exception() is not None:
|
if result.exception() is not None:
|
||||||
raise RuntimeError("Some reload task fails!")
|
raise APIError("Some reload task fails!")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||||
def logs(self, request):
|
def logs(self, request):
|
||||||
"""Return supervisor docker logs."""
|
"""Return supervisor Docker logs."""
|
||||||
return self.sys_supervisor.logs()
|
return self.sys_supervisor.logs()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO util for rest api."""
|
"""Init file for Hass.io util for RESTful API."""
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ from voluptuous.humanize import humanize_error
|
|||||||
from ..const import (
|
from ..const import (
|
||||||
JSON_RESULT, JSON_DATA, JSON_MESSAGE, RESULT_OK, RESULT_ERROR,
|
JSON_RESULT, JSON_DATA, JSON_MESSAGE, RESULT_OK, RESULT_ERROR,
|
||||||
CONTENT_TYPE_BINARY)
|
CONTENT_TYPE_BINARY)
|
||||||
from ..exceptions import HassioError
|
from ..exceptions import HassioError, APIError, APIForbidden
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -21,19 +21,19 @@ def json_loads(data):
|
|||||||
try:
|
try:
|
||||||
return json.loads(data)
|
return json.loads(data)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
raise RuntimeError("Invalid json")
|
raise APIError("Invalid json")
|
||||||
|
|
||||||
|
|
||||||
def api_process(method):
|
def api_process(method):
|
||||||
"""Wrap function with true/false calls to rest api."""
|
"""Wrap function with true/false calls to rest api."""
|
||||||
async def wrap_api(api, *args, **kwargs):
|
async def wrap_api(api, *args, **kwargs):
|
||||||
"""Return api information."""
|
"""Return API information."""
|
||||||
try:
|
try:
|
||||||
answer = await method(api, *args, **kwargs)
|
answer = await method(api, *args, **kwargs)
|
||||||
except HassioError:
|
except (APIError, APIForbidden) as err:
|
||||||
return api_return_error()
|
|
||||||
except RuntimeError as err:
|
|
||||||
return api_return_error(message=str(err))
|
return api_return_error(message=str(err))
|
||||||
|
except HassioError:
|
||||||
|
return api_return_error(message="Unknown Error, see logs")
|
||||||
|
|
||||||
if isinstance(answer, dict):
|
if isinstance(answer, dict):
|
||||||
return api_return_ok(data=answer)
|
return api_return_ok(data=answer)
|
||||||
@ -55,7 +55,7 @@ def api_process_raw(content):
|
|||||||
try:
|
try:
|
||||||
msg_data = await method(api, *args, **kwargs)
|
msg_data = await method(api, *args, **kwargs)
|
||||||
msg_type = content
|
msg_type = content
|
||||||
except RuntimeError as err:
|
except (APIError, APIForbidden) as err:
|
||||||
msg_data = str(err).encode()
|
msg_data = str(err).encode()
|
||||||
msg_type = CONTENT_TYPE_BINARY
|
msg_type = CONTENT_TYPE_BINARY
|
||||||
except HassioError:
|
except HassioError:
|
||||||
@ -90,6 +90,6 @@ async def api_validate(schema, request):
|
|||||||
try:
|
try:
|
||||||
data = schema(data)
|
data = schema(data)
|
||||||
except vol.Invalid as ex:
|
except vol.Invalid as ex:
|
||||||
raise RuntimeError(humanize_error(data, ex)) from None
|
raise APIError(humanize_error(data, ex)) from None
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
26
hassio/api/version.py
Normal file
26
hassio/api/version.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
"""Init file for Hass.io version RESTful API."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from .utils import api_process
|
||||||
|
from ..const import (
|
||||||
|
ATTR_HOMEASSISTANT, ATTR_SUPERVISOR, ATTR_MACHINE, ATTR_ARCH, ATTR_HASSOS,
|
||||||
|
ATTR_CHANNEL)
|
||||||
|
from ..coresys import CoreSysAttributes
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class APIVersion(CoreSysAttributes):
|
||||||
|
"""Handle RESTful API for version functions."""
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def info(self, request):
|
||||||
|
"""Show version info."""
|
||||||
|
return {
|
||||||
|
ATTR_SUPERVISOR: self.sys_supervisor.version,
|
||||||
|
ATTR_HOMEASSISTANT: self.sys_homeassistant.version,
|
||||||
|
ATTR_HASSOS: self.sys_hassos.version,
|
||||||
|
ATTR_MACHINE: self.sys_machine,
|
||||||
|
ATTR_ARCH: self.sys_arch,
|
||||||
|
ATTR_CHANNEL: self.sys_updater.channel,
|
||||||
|
}
|
@ -18,7 +18,7 @@ from .snapshots import SnapshotManager
|
|||||||
from .tasks import Tasks
|
from .tasks import Tasks
|
||||||
from .updater import Updater
|
from .updater import Updater
|
||||||
from .services import ServiceManager
|
from .services import ServiceManager
|
||||||
from .services import Discovery
|
from .discovery import Discovery
|
||||||
from .host import HostManager
|
from .host import HostManager
|
||||||
from .dbus import DBusManager
|
from .dbus import DBusManager
|
||||||
from .hassos import HassOS
|
from .hassos import HassOS
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from ipaddress import ip_network
|
from ipaddress import ip_network
|
||||||
|
|
||||||
HASSIO_VERSION = '131'
|
HASSIO_VERSION = '132'
|
||||||
|
|
||||||
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
|
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
|
||||||
URL_HASSIO_VERSION = \
|
URL_HASSIO_VERSION = \
|
||||||
@ -21,6 +21,7 @@ FILE_HASSIO_CONFIG = Path(HASSIO_DATA, "config.json")
|
|||||||
FILE_HASSIO_HOMEASSISTANT = Path(HASSIO_DATA, "homeassistant.json")
|
FILE_HASSIO_HOMEASSISTANT = Path(HASSIO_DATA, "homeassistant.json")
|
||||||
FILE_HASSIO_UPDATER = Path(HASSIO_DATA, "updater.json")
|
FILE_HASSIO_UPDATER = Path(HASSIO_DATA, "updater.json")
|
||||||
FILE_HASSIO_SERVICES = Path(HASSIO_DATA, "services.json")
|
FILE_HASSIO_SERVICES = Path(HASSIO_DATA, "services.json")
|
||||||
|
FILE_HASSIO_DISCOVERY = Path(HASSIO_DATA, "discovery.json")
|
||||||
|
|
||||||
SOCKET_DOCKER = Path("/var/run/docker.sock")
|
SOCKET_DOCKER = Path("/var/run/docker.sock")
|
||||||
|
|
||||||
@ -74,6 +75,7 @@ ATTR_TYPE = 'type'
|
|||||||
ATTR_SOURCE = 'source'
|
ATTR_SOURCE = 'source'
|
||||||
ATTR_FEATURES = 'features'
|
ATTR_FEATURES = 'features'
|
||||||
ATTR_ADDONS = 'addons'
|
ATTR_ADDONS = 'addons'
|
||||||
|
ATTR_PROVIDERS = 'providers'
|
||||||
ATTR_VERSION = 'version'
|
ATTR_VERSION = 'version'
|
||||||
ATTR_VERSION_LATEST = 'version_latest'
|
ATTR_VERSION_LATEST = 'version_latest'
|
||||||
ATTR_AUTO_UART = 'auto_uart'
|
ATTR_AUTO_UART = 'auto_uart'
|
||||||
@ -107,8 +109,6 @@ ATTR_MAINTAINER = 'maintainer'
|
|||||||
ATTR_PASSWORD = 'password'
|
ATTR_PASSWORD = 'password'
|
||||||
ATTR_TOTP = 'totp'
|
ATTR_TOTP = 'totp'
|
||||||
ATTR_INITIALIZE = 'initialize'
|
ATTR_INITIALIZE = 'initialize'
|
||||||
ATTR_SESSION = 'session'
|
|
||||||
ATTR_SESSIONS = 'sessions'
|
|
||||||
ATTR_LOCATON = 'location'
|
ATTR_LOCATON = 'location'
|
||||||
ATTR_BUILD = 'build'
|
ATTR_BUILD = 'build'
|
||||||
ATTR_DEVICES = 'devices'
|
ATTR_DEVICES = 'devices'
|
||||||
@ -154,7 +154,7 @@ ATTR_MEMORY_LIMIT = 'memory_limit'
|
|||||||
ATTR_MEMORY_USAGE = 'memory_usage'
|
ATTR_MEMORY_USAGE = 'memory_usage'
|
||||||
ATTR_BLK_READ = 'blk_read'
|
ATTR_BLK_READ = 'blk_read'
|
||||||
ATTR_BLK_WRITE = 'blk_write'
|
ATTR_BLK_WRITE = 'blk_write'
|
||||||
ATTR_PROVIDER = 'provider'
|
ATTR_ADDON = 'addon'
|
||||||
ATTR_AVAILABLE = 'available'
|
ATTR_AVAILABLE = 'available'
|
||||||
ATTR_HOST = 'host'
|
ATTR_HOST = 'host'
|
||||||
ATTR_USERNAME = 'username'
|
ATTR_USERNAME = 'username'
|
||||||
@ -163,8 +163,8 @@ ATTR_DISCOVERY = 'discovery'
|
|||||||
ATTR_PLATFORM = 'platform'
|
ATTR_PLATFORM = 'platform'
|
||||||
ATTR_COMPONENT = 'component'
|
ATTR_COMPONENT = 'component'
|
||||||
ATTR_CONFIG = 'config'
|
ATTR_CONFIG = 'config'
|
||||||
ATTR_DISCOVERY_ID = 'discovery_id'
|
|
||||||
ATTR_SERVICES = 'services'
|
ATTR_SERVICES = 'services'
|
||||||
|
ATTR_SERVICE = 'service'
|
||||||
ATTR_DISCOVERY = 'discovery'
|
ATTR_DISCOVERY = 'discovery'
|
||||||
ATTR_PROTECTED = 'protected'
|
ATTR_PROTECTED = 'protected'
|
||||||
ATTR_CRYPTO = 'crypto'
|
ATTR_CRYPTO = 'crypto'
|
||||||
@ -185,8 +185,12 @@ ATTR_FULL_ACCESS = 'full_access'
|
|||||||
ATTR_PROTECTED = 'protected'
|
ATTR_PROTECTED = 'protected'
|
||||||
ATTR_RATING = 'rating'
|
ATTR_RATING = 'rating'
|
||||||
ATTR_HASSIO_ROLE = 'hassio_role'
|
ATTR_HASSIO_ROLE = 'hassio_role'
|
||||||
|
ATTR_SUPERVISOR = 'supervisor'
|
||||||
|
|
||||||
SERVICE_MQTT = 'mqtt'
|
SERVICE_MQTT = 'mqtt'
|
||||||
|
PROVIDE_SERVICE = 'provide'
|
||||||
|
NEED_SERVICE = 'need'
|
||||||
|
WANT_SERVICE = 'want'
|
||||||
|
|
||||||
STARTUP_INITIALIZE = 'initialize'
|
STARTUP_INITIALIZE = 'initialize'
|
||||||
STARTUP_SYSTEM = 'system'
|
STARTUP_SYSTEM = 'system'
|
||||||
|
@ -52,6 +52,9 @@ class HassIO(CoreSysAttributes):
|
|||||||
# load services
|
# load services
|
||||||
await self.sys_services.load()
|
await self.sys_services.load()
|
||||||
|
|
||||||
|
# Load discovery
|
||||||
|
await self.sys_discovery.load()
|
||||||
|
|
||||||
# start dns forwarding
|
# start dns forwarding
|
||||||
self.sys_create_task(self.sys_dns.start())
|
self.sys_create_task(self.sys_dns.start())
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ class Systemd(DBusInterface):
|
|||||||
"""Systemd function handler."""
|
"""Systemd function handler."""
|
||||||
|
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
"""Connect do bus."""
|
"""Connect to D-Bus."""
|
||||||
try:
|
try:
|
||||||
self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT)
|
self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT)
|
||||||
except DBusError:
|
except DBusError:
|
||||||
|
123
hassio/discovery.py
Normal file
123
hassio/discovery.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
"""Handle discover message for Home Assistant."""
|
||||||
|
import logging
|
||||||
|
from contextlib import suppress
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import attr
|
||||||
|
import voluptuous as vol
|
||||||
|
from voluptuous.humanize import humanize_error
|
||||||
|
|
||||||
|
from .const import FILE_HASSIO_DISCOVERY, ATTR_CONFIG, ATTR_DISCOVERY
|
||||||
|
from .coresys import CoreSysAttributes
|
||||||
|
from .exceptions import DiscoveryError, HomeAssistantAPIError
|
||||||
|
from .validate import SCHEMA_DISCOVERY_CONFIG
|
||||||
|
from .utils.json import JsonConfig
|
||||||
|
from .services.validate import DISCOVERY_SERVICES
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CMD_NEW = 'post'
|
||||||
|
CMD_DEL = 'delete'
|
||||||
|
|
||||||
|
|
||||||
|
class Discovery(CoreSysAttributes, JsonConfig):
|
||||||
|
"""Home Assistant Discovery handler."""
|
||||||
|
|
||||||
|
def __init__(self, coresys):
|
||||||
|
"""Initialize discovery handler."""
|
||||||
|
super().__init__(FILE_HASSIO_DISCOVERY, SCHEMA_DISCOVERY_CONFIG)
|
||||||
|
self.coresys = coresys
|
||||||
|
self.message_obj = {}
|
||||||
|
|
||||||
|
async def load(self):
|
||||||
|
"""Load exists discovery message into storage."""
|
||||||
|
messages = {}
|
||||||
|
for message in self._data[ATTR_DISCOVERY]:
|
||||||
|
discovery = Message(**message)
|
||||||
|
messages[discovery.uuid] = discovery
|
||||||
|
|
||||||
|
self.message_obj = messages
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""Write discovery message into data file."""
|
||||||
|
messages = []
|
||||||
|
for message in self.list_messages:
|
||||||
|
messages.append(attr.asdict(message))
|
||||||
|
|
||||||
|
self._data[ATTR_DISCOVERY].clear()
|
||||||
|
self._data[ATTR_DISCOVERY].extend(messages)
|
||||||
|
self.save_data()
|
||||||
|
|
||||||
|
def get(self, uuid):
|
||||||
|
"""Return discovery message."""
|
||||||
|
return self.message_obj.get(uuid)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def list_messages(self):
|
||||||
|
"""Return list of available discovery messages."""
|
||||||
|
return list(self.message_obj.values())
|
||||||
|
|
||||||
|
def send(self, addon, service, component, platform, config):
|
||||||
|
"""Send a discovery message to Home Assistant."""
|
||||||
|
try:
|
||||||
|
DISCOVERY_SERVICES[service](config)
|
||||||
|
except vol.Invalid as err:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Invalid discovery %s config", humanize_error(config, err))
|
||||||
|
raise DiscoveryError() from None
|
||||||
|
|
||||||
|
# Create message
|
||||||
|
message = Message(addon.slug, service, component, platform, config)
|
||||||
|
|
||||||
|
# Already exists?
|
||||||
|
for old_message in self.list_messages:
|
||||||
|
if old_message != message:
|
||||||
|
continue
|
||||||
|
_LOGGER.warning("Duplicate discovery message from %s", addon.slug)
|
||||||
|
return old_message
|
||||||
|
|
||||||
|
_LOGGER.info("Send discovery to Home Assistant %s/%s from %s",
|
||||||
|
component, platform, addon.slug)
|
||||||
|
self.message_obj[message.uuid] = message
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
self.sys_create_task(self._push_discovery(message, CMD_NEW))
|
||||||
|
return message
|
||||||
|
|
||||||
|
def remove(self, message):
|
||||||
|
"""Remove a discovery message from Home Assistant."""
|
||||||
|
self.message_obj.pop(message.uuid, None)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
_LOGGER.info("Delete discovery to Home Assistant %s/%s from %s",
|
||||||
|
message.component, message.platform, message.addon)
|
||||||
|
self.sys_create_task(self._push_discovery(message, CMD_DEL))
|
||||||
|
|
||||||
|
async def _push_discovery(self, message, command):
|
||||||
|
"""Send a discovery request."""
|
||||||
|
if not await self.sys_homeassistant.check_api_state():
|
||||||
|
_LOGGER.info("Discovery %s mesage ignore", message.uuid)
|
||||||
|
return
|
||||||
|
|
||||||
|
data = attr.asdict(message)
|
||||||
|
data.pop(ATTR_CONFIG)
|
||||||
|
|
||||||
|
with suppress(HomeAssistantAPIError):
|
||||||
|
async with self.sys_homeassistant.make_request(
|
||||||
|
command, f"api/hassio_push/discovery/{message.uuid}",
|
||||||
|
json=data, timeout=10):
|
||||||
|
_LOGGER.info("Discovery %s message send", message.uuid)
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.warning("Discovery %s message fail", message.uuid)
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class Message:
|
||||||
|
"""Represent a single Discovery message."""
|
||||||
|
addon = attr.ib()
|
||||||
|
service = attr.ib()
|
||||||
|
component = attr.ib()
|
||||||
|
platform = attr.ib()
|
||||||
|
config = attr.ib(cmp=False)
|
||||||
|
uuid = attr.ib(factory=lambda: uuid4().hex, cmp=False)
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO docker object."""
|
"""Init file for Hass.io Docker object."""
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -15,13 +15,13 @@ CommandReturn = attr.make_class('CommandReturn', ['exit_code', 'output'])
|
|||||||
|
|
||||||
|
|
||||||
class DockerAPI:
|
class DockerAPI:
|
||||||
"""Docker hassio wrapper.
|
"""Docker Hass.io wrapper.
|
||||||
|
|
||||||
This class is not AsyncIO safe!
|
This class is not AsyncIO safe!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize docker base wrapper."""
|
"""Initialize Docker base wrapper."""
|
||||||
self.docker = docker.DockerClient(
|
self.docker = docker.DockerClient(
|
||||||
base_url="unix:/{}".format(str(SOCKET_DOCKER)),
|
base_url="unix:/{}".format(str(SOCKET_DOCKER)),
|
||||||
version='auto', timeout=900)
|
version='auto', timeout=900)
|
||||||
@ -29,21 +29,21 @@ class DockerAPI:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def images(self):
|
def images(self):
|
||||||
"""Return api images."""
|
"""Return API images."""
|
||||||
return self.docker.images
|
return self.docker.images
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def containers(self):
|
def containers(self):
|
||||||
"""Return api containers."""
|
"""Return API containers."""
|
||||||
return self.docker.containers
|
return self.docker.containers
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def api(self):
|
def api(self):
|
||||||
"""Return api containers."""
|
"""Return API containers."""
|
||||||
return self.docker.api
|
return self.docker.api
|
||||||
|
|
||||||
def run(self, image, **kwargs):
|
def run(self, image, **kwargs):
|
||||||
""""Create a docker and run it.
|
""""Create a Docker container and run it.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
@ -51,7 +51,7 @@ class DockerAPI:
|
|||||||
network_mode = kwargs.get('network_mode')
|
network_mode = kwargs.get('network_mode')
|
||||||
hostname = kwargs.get('hostname')
|
hostname = kwargs.get('hostname')
|
||||||
|
|
||||||
# setup network
|
# Setup network
|
||||||
kwargs['dns_search'] = ["."]
|
kwargs['dns_search'] = ["."]
|
||||||
if network_mode:
|
if network_mode:
|
||||||
kwargs['dns'] = [str(self.network.supervisor)]
|
kwargs['dns'] = [str(self.network.supervisor)]
|
||||||
@ -59,7 +59,7 @@ class DockerAPI:
|
|||||||
else:
|
else:
|
||||||
kwargs['network'] = None
|
kwargs['network'] = None
|
||||||
|
|
||||||
# create container
|
# Create container
|
||||||
try:
|
try:
|
||||||
container = self.docker.containers.create(image, **kwargs)
|
container = self.docker.containers.create(image, **kwargs)
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO addon docker object."""
|
"""Init file for Hass.io add-on Docker object."""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -19,45 +19,45 @@ AUDIO_DEVICE = "/dev/snd:/dev/snd:rwm"
|
|||||||
|
|
||||||
|
|
||||||
class DockerAddon(DockerInterface):
|
class DockerAddon(DockerInterface):
|
||||||
"""Docker hassio wrapper for HomeAssistant."""
|
"""Docker Hass.io wrapper for Home Assistant."""
|
||||||
|
|
||||||
def __init__(self, coresys, slug):
|
def __init__(self, coresys, slug):
|
||||||
"""Initialize docker homeassistant wrapper."""
|
"""Initialize Docker Home Assistant wrapper."""
|
||||||
super().__init__(coresys)
|
super().__init__(coresys)
|
||||||
self._id = slug
|
self._id = slug
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def addon(self):
|
def addon(self):
|
||||||
"""Return addon of docker image."""
|
"""Return add-on of Docker image."""
|
||||||
return self.sys_addons.get(self._id)
|
return self.sys_addons.get(self._id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image(self):
|
def image(self):
|
||||||
"""Return name of docker image."""
|
"""Return name of Docker image."""
|
||||||
return self.addon.image
|
return self.addon.image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timeout(self):
|
def timeout(self):
|
||||||
"""Return timeout for docker actions."""
|
"""Return timeout for Docker actions."""
|
||||||
return self.addon.timeout
|
return self.addon.timeout
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def version(self):
|
def version(self):
|
||||||
"""Return version of docker image."""
|
"""Return version of Docker image."""
|
||||||
if not self.addon.legacy:
|
if not self.addon.legacy:
|
||||||
return super().version
|
return super().version
|
||||||
return self.addon.version_installed
|
return self.addon.version_installed
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arch(self):
|
def arch(self):
|
||||||
"""Return arch of docker image."""
|
"""Return arch of Docker image."""
|
||||||
if not self.addon.legacy:
|
if not self.addon.legacy:
|
||||||
return super().arch
|
return super().arch
|
||||||
return self.sys_arch
|
return self.sys_arch
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return name of docker container."""
|
"""Return name of Docker container."""
|
||||||
return "addon_{}".format(self.addon.slug)
|
return "addon_{}".format(self.addon.slug)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -74,12 +74,12 @@ class DockerAddon(DockerInterface):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def hostname(self):
|
def hostname(self):
|
||||||
"""Return slug/id of addon."""
|
"""Return slug/id of add-on."""
|
||||||
return self.addon.slug.replace('_', '-')
|
return self.addon.slug.replace('_', '-')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def environment(self):
|
def environment(self):
|
||||||
"""Return environment for docker add-on."""
|
"""Return environment for Docker add-on."""
|
||||||
addon_env = self.addon.environment or {}
|
addon_env = self.addon.environment or {}
|
||||||
|
|
||||||
# Need audio settings
|
# Need audio settings
|
||||||
@ -114,7 +114,7 @@ class DockerAddon(DockerInterface):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def ports(self):
|
def ports(self):
|
||||||
"""Filter None from addon ports."""
|
"""Filter None from add-on ports."""
|
||||||
if not self.addon.ports:
|
if not self.addon.ports:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ class DockerAddon(DockerInterface):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def security_opt(self):
|
def security_opt(self):
|
||||||
"""Controlling security opt."""
|
"""Controlling security options."""
|
||||||
security = []
|
security = []
|
||||||
|
|
||||||
# AppArmor
|
# AppArmor
|
||||||
@ -144,7 +144,7 @@ class DockerAddon(DockerInterface):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def tmpfs(self):
|
def tmpfs(self):
|
||||||
"""Return tmpfs for docker add-on."""
|
"""Return tmpfs for Docker add-on."""
|
||||||
options = self.addon.tmpfs
|
options = self.addon.tmpfs
|
||||||
if options:
|
if options:
|
||||||
return {"/tmpfs": f"{options}"}
|
return {"/tmpfs": f"{options}"}
|
||||||
@ -160,14 +160,14 @@ class DockerAddon(DockerInterface):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def network_mode(self):
|
def network_mode(self):
|
||||||
"""Return network mode for addon."""
|
"""Return network mode for add-on."""
|
||||||
if self.addon.host_network:
|
if self.addon.host_network:
|
||||||
return 'host'
|
return 'host'
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pid_mode(self):
|
def pid_mode(self):
|
||||||
"""Return PID mode for addon."""
|
"""Return PID mode for add-on."""
|
||||||
if not self.addon.protected and self.addon.host_pid:
|
if not self.addon.protected and self.addon.host_pid:
|
||||||
return 'host'
|
return 'host'
|
||||||
return None
|
return None
|
||||||
@ -242,7 +242,7 @@ class DockerAddon(DockerInterface):
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
# Host dbus system
|
# Host D-Bus system
|
||||||
if self.addon.host_dbus:
|
if self.addon.host_dbus:
|
||||||
volumes.update({
|
volumes.update({
|
||||||
"/var/run/dbus": {
|
"/var/run/dbus": {
|
||||||
@ -259,7 +259,7 @@ class DockerAddon(DockerInterface):
|
|||||||
return volumes
|
return volumes
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
"""Run docker image.
|
"""Run Docker image.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
@ -269,7 +269,7 @@ class DockerAddon(DockerInterface):
|
|||||||
# Security check
|
# Security check
|
||||||
if not self.addon.protected:
|
if not self.addon.protected:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"%s run with disabled proteced mode!", self.addon.name)
|
"%s run with disabled protected mode!", self.addon.name)
|
||||||
|
|
||||||
# cleanup
|
# cleanup
|
||||||
self._stop()
|
self._stop()
|
||||||
@ -296,13 +296,13 @@ class DockerAddon(DockerInterface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if ret:
|
if ret:
|
||||||
_LOGGER.info("Start docker addon %s with version %s",
|
_LOGGER.info("Start Docker add-on %s with version %s",
|
||||||
self.image, self.version)
|
self.image, self.version)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _install(self, tag):
|
def _install(self, tag):
|
||||||
"""Pull docker image or build it.
|
"""Pull Docker image or build it.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
@ -312,7 +312,7 @@ class DockerAddon(DockerInterface):
|
|||||||
return super()._install(tag)
|
return super()._install(tag)
|
||||||
|
|
||||||
def _build(self, tag):
|
def _build(self, tag):
|
||||||
"""Build a docker container.
|
"""Build a Docker container.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
@ -329,7 +329,7 @@ class DockerAddon(DockerInterface):
|
|||||||
# Update meta data
|
# Update meta data
|
||||||
self._meta = image.attrs
|
self._meta = image.attrs
|
||||||
|
|
||||||
except (docker.errors.DockerException) as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.error("Can't build %s:%s: %s", self.image, tag, err)
|
_LOGGER.error("Can't build %s:%s: %s", self.image, tag, err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -403,7 +403,7 @@ class DockerAddon(DockerInterface):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# load needed docker objects
|
# Load needed docker objects
|
||||||
container = self.sys_docker.containers.get(self.name)
|
container = self.sys_docker.containers.get(self.name)
|
||||||
socket = container.attach_socket(params={'stdin': 1, 'stream': 1})
|
socket = container.attach_socket(params={'stdin': 1, 'stream': 1})
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
@ -411,7 +411,7 @@ class DockerAddon(DockerInterface):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# write to stdin
|
# Write to stdin
|
||||||
data += b"\n"
|
data += b"\n"
|
||||||
os.write(socket.fileno(), data)
|
os.write(socket.fileno(), data)
|
||||||
socket.close()
|
socket.close()
|
||||||
|
@ -10,11 +10,11 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class DockerHassOSCli(DockerInterface, CoreSysAttributes):
|
class DockerHassOSCli(DockerInterface, CoreSysAttributes):
|
||||||
"""Docker hassio wrapper for HassOS Cli."""
|
"""Docker Hass.io wrapper for HassOS Cli."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image(self):
|
def image(self):
|
||||||
"""Return name of HassOS cli image."""
|
"""Return name of HassOS CLI image."""
|
||||||
return f"homeassistant/{self.sys_arch}-hassio-cli"
|
return f"homeassistant/{self.sys_arch}-hassio-cli"
|
||||||
|
|
||||||
def _stop(self):
|
def _stop(self):
|
||||||
@ -22,16 +22,16 @@ class DockerHassOSCli(DockerInterface, CoreSysAttributes):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _attach(self):
|
def _attach(self):
|
||||||
"""Attach to running docker container.
|
"""Attach to running Docker container.
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
image = self.sys_docker.images.get(self.image)
|
image = self.sys_docker.images.get(self.image)
|
||||||
|
|
||||||
except docker.errors.DockerException:
|
except docker.errors.DockerException:
|
||||||
_LOGGER.warning("Can't find a HassOS cli %s", self.image)
|
_LOGGER.warning("Can't find a HassOS CLI %s", self.image)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self._meta = image.attrs
|
self._meta = image.attrs
|
||||||
_LOGGER.info("Found HassOS cli %s with version %s",
|
_LOGGER.info("Found HassOS CLI %s with version %s",
|
||||||
self.image, self.version)
|
self.image, self.version)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO docker object."""
|
"""Init file for Hass.io Docker object."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
@ -12,35 +12,35 @@ HASS_DOCKER_NAME = 'homeassistant'
|
|||||||
|
|
||||||
|
|
||||||
class DockerHomeAssistant(DockerInterface):
|
class DockerHomeAssistant(DockerInterface):
|
||||||
"""Docker hassio wrapper for HomeAssistant."""
|
"""Docker Hass.io wrapper for Home Assistant."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def machine(self):
|
def machine(self):
|
||||||
"""Return machine of Home-Assistant docker image."""
|
"""Return machine of Home Assistant Docker image."""
|
||||||
if self._meta and LABEL_MACHINE in self._meta['Config']['Labels']:
|
if self._meta and LABEL_MACHINE in self._meta['Config']['Labels']:
|
||||||
return self._meta['Config']['Labels'][LABEL_MACHINE]
|
return self._meta['Config']['Labels'][LABEL_MACHINE]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image(self):
|
def image(self):
|
||||||
"""Return name of docker image."""
|
"""Return name of Docker image."""
|
||||||
return self.sys_homeassistant.image
|
return self.sys_homeassistant.image
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return name of docker container."""
|
"""Return name of Docker container."""
|
||||||
return HASS_DOCKER_NAME
|
return HASS_DOCKER_NAME
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def devices(self):
|
def devices(self):
|
||||||
"""Create list of special device to map into docker."""
|
"""Create list of special device to map into Docker."""
|
||||||
devices = []
|
devices = []
|
||||||
for device in self.sys_hardware.serial_devices:
|
for device in self.sys_hardware.serial_devices:
|
||||||
devices.append(f"{device}:{device}:rwm")
|
devices.append(f"{device}:{device}:rwm")
|
||||||
return devices or None
|
return devices or None
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
"""Run docker image.
|
"""Run Docker image.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
@ -108,7 +108,7 @@ class DockerHomeAssistant(DockerInterface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def is_initialize(self):
|
def is_initialize(self):
|
||||||
"""Return True if docker container exists."""
|
"""Return True if Docker container exists."""
|
||||||
return self.sys_run_in_executor(self._is_initialize)
|
return self.sys_run_in_executor(self._is_initialize)
|
||||||
|
|
||||||
def _is_initialize(self):
|
def _is_initialize(self):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Interface class for HassIO docker object."""
|
"""Interface class for Hass.io Docker object."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import logging
|
import logging
|
||||||
@ -14,27 +14,27 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class DockerInterface(CoreSysAttributes):
|
class DockerInterface(CoreSysAttributes):
|
||||||
"""Docker hassio interface."""
|
"""Docker Hass.io interface."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys):
|
||||||
"""Initialize docker base wrapper."""
|
"""Initialize Docker base wrapper."""
|
||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
self._meta = None
|
self._meta = None
|
||||||
self.lock = asyncio.Lock(loop=coresys.loop)
|
self.lock = asyncio.Lock(loop=coresys.loop)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timeout(self):
|
def timeout(self):
|
||||||
"""Return timeout for docker actions."""
|
"""Return timeout for Docker actions."""
|
||||||
return 30
|
return 30
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return name of docker container."""
|
"""Return name of Docker container."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def meta_config(self):
|
def meta_config(self):
|
||||||
"""Return meta data of config for container/image."""
|
"""Return meta data of configuration for container/image."""
|
||||||
if not self._meta:
|
if not self._meta:
|
||||||
return {}
|
return {}
|
||||||
return self._meta.get('Config', {})
|
return self._meta.get('Config', {})
|
||||||
@ -42,21 +42,21 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
@property
|
@property
|
||||||
def meta_labels(self):
|
def meta_labels(self):
|
||||||
"""Return meta data of labels for container/image."""
|
"""Return meta data of labels for container/image."""
|
||||||
return self.meta_config.get('Labels', {})
|
return self.meta_config.get('Labels') or {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image(self):
|
def image(self):
|
||||||
"""Return name of docker image."""
|
"""Return name of Docker image."""
|
||||||
return self.meta_config.get('Image')
|
return self.meta_config.get('Image')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def version(self):
|
def version(self):
|
||||||
"""Return version of docker image."""
|
"""Return version of Docker image."""
|
||||||
return self.meta_labels.get(LABEL_VERSION)
|
return self.meta_labels.get(LABEL_VERSION)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arch(self):
|
def arch(self):
|
||||||
"""Return arch of docker image."""
|
"""Return arch of Docker image."""
|
||||||
return self.meta_labels.get(LABEL_ARCH)
|
return self.meta_labels.get(LABEL_ARCH)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -70,7 +70,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
return self.sys_run_in_executor(self._install, tag)
|
return self.sys_run_in_executor(self._install, tag)
|
||||||
|
|
||||||
def _install(self, tag):
|
def _install(self, tag):
|
||||||
"""Pull docker image.
|
"""Pull Docker image.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
@ -88,11 +88,11 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def exists(self):
|
def exists(self):
|
||||||
"""Return True if docker image exists in local repo."""
|
"""Return True if Docker image exists in local repository."""
|
||||||
return self.sys_run_in_executor(self._exists)
|
return self.sys_run_in_executor(self._exists)
|
||||||
|
|
||||||
def _exists(self):
|
def _exists(self):
|
||||||
"""Return True if docker image exists in local repo.
|
"""Return True if Docker image exists in local repository.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
@ -105,14 +105,14 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def is_running(self):
|
def is_running(self):
|
||||||
"""Return True if docker is Running.
|
"""Return True if Docker is running.
|
||||||
|
|
||||||
Return a Future.
|
Return a Future.
|
||||||
"""
|
"""
|
||||||
return self.sys_run_in_executor(self._is_running)
|
return self.sys_run_in_executor(self._is_running)
|
||||||
|
|
||||||
def _is_running(self):
|
def _is_running(self):
|
||||||
"""Return True if docker is Running.
|
"""Return True if Docker is running.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
@ -134,7 +134,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
def attach(self):
|
def attach(self):
|
||||||
"""Attach to running docker container."""
|
"""Attach to running Docker container."""
|
||||||
return self.sys_run_in_executor(self._attach)
|
return self.sys_run_in_executor(self._attach)
|
||||||
|
|
||||||
def _attach(self):
|
def _attach(self):
|
||||||
@ -157,11 +157,11 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Run docker image."""
|
"""Run Docker image."""
|
||||||
return self.sys_run_in_executor(self._run)
|
return self.sys_run_in_executor(self._run)
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
"""Run docker image.
|
"""Run Docker image.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
@ -169,7 +169,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Stop/remove docker container."""
|
"""Stop/remove Docker container."""
|
||||||
return self.sys_run_in_executor(self._stop)
|
return self.sys_run_in_executor(self._stop)
|
||||||
|
|
||||||
def _stop(self):
|
def _stop(self):
|
||||||
@ -183,19 +183,19 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
if container.status == 'running':
|
if container.status == 'running':
|
||||||
_LOGGER.info("Stop %s docker application", self.image)
|
_LOGGER.info("Stop %s Docker application", self.image)
|
||||||
with suppress(docker.errors.DockerException):
|
with suppress(docker.errors.DockerException):
|
||||||
container.stop(timeout=self.timeout)
|
container.stop(timeout=self.timeout)
|
||||||
|
|
||||||
with suppress(docker.errors.DockerException):
|
with suppress(docker.errors.DockerException):
|
||||||
_LOGGER.info("Clean %s docker application", self.image)
|
_LOGGER.info("Clean %s Docker application", self.image)
|
||||||
container.remove(force=True)
|
container.remove(force=True)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
def remove(self):
|
def remove(self):
|
||||||
"""Remove docker images."""
|
"""Remove Docker images."""
|
||||||
return self.sys_run_in_executor(self._remove)
|
return self.sys_run_in_executor(self._remove)
|
||||||
|
|
||||||
def _remove(self):
|
def _remove(self):
|
||||||
@ -203,11 +203,11 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
# cleanup container
|
# Cleanup container
|
||||||
self._stop()
|
self._stop()
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Remove docker %s with latest and %s", self.image, self.version)
|
"Remove Docker %s with latest and %s", self.image, self.version)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with suppress(docker.errors.ImageNotFound):
|
with suppress(docker.errors.ImageNotFound):
|
||||||
@ -227,7 +227,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
def update(self, tag):
|
def update(self, tag):
|
||||||
"""Update a docker image."""
|
"""Update a Docker image."""
|
||||||
return self.sys_run_in_executor(self._update, tag)
|
return self.sys_run_in_executor(self._update, tag)
|
||||||
|
|
||||||
def _update(self, tag):
|
def _update(self, tag):
|
||||||
@ -236,27 +236,27 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Update docker %s with %s:%s", self.version, self.image, tag)
|
"Update Docker %s with %s:%s", self.version, self.image, tag)
|
||||||
|
|
||||||
# update docker image
|
# Update docker image
|
||||||
if not self._install(tag):
|
if not self._install(tag):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# stop container & cleanup
|
# Stop container & cleanup
|
||||||
self._stop()
|
self._stop()
|
||||||
self._cleanup()
|
self._cleanup()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def logs(self):
|
def logs(self):
|
||||||
"""Return docker logs of container.
|
"""Return Docker logs of container.
|
||||||
|
|
||||||
Return a Future.
|
Return a Future.
|
||||||
"""
|
"""
|
||||||
return self.sys_run_in_executor(self._logs)
|
return self.sys_run_in_executor(self._logs)
|
||||||
|
|
||||||
def _logs(self):
|
def _logs(self):
|
||||||
"""Return docker logs of container.
|
"""Return Docker logs of container.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
@ -268,7 +268,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
try:
|
try:
|
||||||
return container.logs(tail=100, stdout=True, stderr=True)
|
return container.logs(tail=100, stdout=True, stderr=True)
|
||||||
except docker.errors.DockerException as err:
|
except docker.errors.DockerException as err:
|
||||||
_LOGGER.warning("Can't grap logs from %s: %s", self.image, err)
|
_LOGGER.warning("Can't grep logs from %s: %s", self.image, err)
|
||||||
|
|
||||||
@process_lock
|
@process_lock
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
@ -291,7 +291,7 @@ class DockerInterface(CoreSysAttributes):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
with suppress(docker.errors.DockerException):
|
with suppress(docker.errors.DockerException):
|
||||||
_LOGGER.info("Cleanup docker images: %s", image.tags)
|
_LOGGER.info("Cleanup Docker images: %s", image.tags)
|
||||||
self.sys_docker.images.remove(image.id, force=True)
|
self.sys_docker.images.remove(image.id, force=True)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Internal network manager for HassIO."""
|
"""Internal network manager for Hass.io."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
@ -9,13 +9,13 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class DockerNetwork:
|
class DockerNetwork:
|
||||||
"""Internal HassIO Network.
|
"""Internal Hass.io Network.
|
||||||
|
|
||||||
This class is not AsyncIO safe!
|
This class is not AsyncIO safe!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, dock):
|
def __init__(self, dock):
|
||||||
"""Initialize internal hassio network."""
|
"""Initialize internal Hass.io network."""
|
||||||
self.docker = dock
|
self.docker = dock
|
||||||
self.network = self._get_network()
|
self.network = self._get_network()
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ class DockerNetwork:
|
|||||||
try:
|
try:
|
||||||
return self.docker.networks.get(DOCKER_NETWORK)
|
return self.docker.networks.get(DOCKER_NETWORK)
|
||||||
except docker.errors.NotFound:
|
except docker.errors.NotFound:
|
||||||
_LOGGER.info("Can't find HassIO network, create new network")
|
_LOGGER.info("Can't find Hass.io network, create new network")
|
||||||
|
|
||||||
ipam_pool = docker.types.IPAMPool(
|
ipam_pool = docker.types.IPAMPool(
|
||||||
subnet=str(DOCKER_NETWORK_MASK),
|
subnet=str(DOCKER_NETWORK_MASK),
|
||||||
@ -61,7 +61,7 @@ class DockerNetwork:
|
|||||||
})
|
})
|
||||||
|
|
||||||
def attach_container(self, container, alias=None, ipv4=None):
|
def attach_container(self, container, alias=None, ipv4=None):
|
||||||
"""Attach container to hassio network.
|
"""Attach container to Hass.io network.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
@ -77,7 +77,7 @@ class DockerNetwork:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def detach_default_bridge(self, container):
|
def detach_default_bridge(self, container):
|
||||||
"""Detach default docker bridge.
|
"""Detach default Docker bridge.
|
||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Calc & represent docker stats data."""
|
"""Calc and represent docker stats data."""
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
|
|
||||||
@ -6,7 +6,7 @@ class DockerStats:
|
|||||||
"""Hold stats data from container inside."""
|
"""Hold stats data from container inside."""
|
||||||
|
|
||||||
def __init__(self, stats):
|
def __init__(self, stats):
|
||||||
"""Initialize docker stats."""
|
"""Initialize Docker stats."""
|
||||||
self._cpu = 0.0
|
self._cpu = 0.0
|
||||||
self._network_rx = 0
|
self._network_rx = 0
|
||||||
self._network_tx = 0
|
self._network_tx = 0
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Init file for HassIO docker object."""
|
"""Init file for Hass.io Docker object."""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@ -11,11 +11,11 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class DockerSupervisor(DockerInterface, CoreSysAttributes):
|
class DockerSupervisor(DockerInterface, CoreSysAttributes):
|
||||||
"""Docker hassio wrapper for Supervisor."""
|
"""Docker Hass.io wrapper for Supervisor."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return name of docker container."""
|
"""Return name of Docker container."""
|
||||||
return os.environ['SUPERVISOR_NAME']
|
return os.environ['SUPERVISOR_NAME']
|
||||||
|
|
||||||
def _attach(self):
|
def _attach(self):
|
||||||
@ -29,14 +29,14 @@ class DockerSupervisor(DockerInterface, CoreSysAttributes):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
self._meta = container.attrs
|
self._meta = container.attrs
|
||||||
_LOGGER.info("Attach to supervisor %s with version %s",
|
_LOGGER.info("Attach to Supervisor %s with version %s",
|
||||||
self.image, self.version)
|
self.image, self.version)
|
||||||
|
|
||||||
# if already attach
|
# If already attach
|
||||||
if container in self.sys_docker.network.containers:
|
if container in self.sys_docker.network.containers:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# attach to network
|
# Attach to network
|
||||||
return self.sys_docker.network.attach_container(
|
return self.sys_docker.network.attach_container(
|
||||||
container, alias=['hassio'],
|
container, alias=['hassio'],
|
||||||
ipv4=self.sys_docker.network.supervisor)
|
ipv4=self.sys_docker.network.supervisor)
|
||||||
|
@ -81,13 +81,25 @@ class HostAppArmorError(HostError):
|
|||||||
|
|
||||||
# API
|
# API
|
||||||
|
|
||||||
class APIError(HassioError):
|
class APIError(HassioError, RuntimeError):
|
||||||
"""API errors."""
|
"""API errors."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class APINotSupportedError(HassioNotSupportedError):
|
class APIForbidden(APIError):
|
||||||
"""API not supported error."""
|
"""API forbidden error."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Service / Discovery
|
||||||
|
|
||||||
|
class DiscoveryError(HassioError):
|
||||||
|
"""Discovery Errors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ServicesError(HassioError):
|
||||||
|
"""Services Errors."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -439,19 +439,6 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
|||||||
_LOGGER.warning("Home Assistant API config mismatch: %d", err)
|
_LOGGER.warning("Home Assistant API config mismatch: %d", err)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def send_event(self, event_type, event_data=None):
|
|
||||||
"""Send event to Home-Assistant."""
|
|
||||||
with suppress(HomeAssistantAPIError):
|
|
||||||
async with self.make_request(
|
|
||||||
'get', f'api/events/{event_type}'
|
|
||||||
) as resp:
|
|
||||||
if resp.status in (200, 201):
|
|
||||||
return
|
|
||||||
err = resp.status
|
|
||||||
|
|
||||||
_LOGGER.warning("Home Assistant event %s fails: %s", event_type, err)
|
|
||||||
return HomeAssistantError()
|
|
||||||
|
|
||||||
async def _block_till_run(self):
|
async def _block_till_run(self):
|
||||||
"""Block until Home-Assistant is booting up or startup timeout."""
|
"""Block until Home-Assistant is booting up or startup timeout."""
|
||||||
start_time = time.monotonic()
|
start_time = time.monotonic()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Host function like audio/dbus/systemd."""
|
"""Host function like audio, D-Bus or systemd."""
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ class HostManager(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def apparmor(self):
|
def apparmor(self):
|
||||||
"""Return host apparmor handler."""
|
"""Return host AppArmor handler."""
|
||||||
return self._apparmor
|
return self._apparmor
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Host Audio-support."""
|
"""Host Audio support."""
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -19,7 +19,7 @@ class AlsaAudio(CoreSysAttributes):
|
|||||||
"""Handle Audio ALSA host data."""
|
"""Handle Audio ALSA host data."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys):
|
||||||
"""Initialize Alsa audio system."""
|
"""Initialize ALSA audio system."""
|
||||||
self.coresys = coresys
|
self.coresys = coresys
|
||||||
self._data = {
|
self._data = {
|
||||||
ATTR_INPUT: {},
|
ATTR_INPUT: {},
|
||||||
|
@ -13,7 +13,7 @@ SYSTEMD_SERVICES = {'hassos-apparmor.service', 'hassio-apparmor.service'}
|
|||||||
|
|
||||||
|
|
||||||
class AppArmorControl(CoreSysAttributes):
|
class AppArmorControl(CoreSysAttributes):
|
||||||
"""Handle host apparmor controls."""
|
"""Handle host AppArmor controls."""
|
||||||
|
|
||||||
def __init__(self, coresys):
|
def __init__(self, coresys):
|
||||||
"""Initialize host power handling."""
|
"""Initialize host power handling."""
|
||||||
@ -23,7 +23,7 @@ class AppArmorControl(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
"""Return True if AppArmor is availabe on host."""
|
"""Return True if AppArmor is available on host."""
|
||||||
return self._service is not None
|
return self._service is not None
|
||||||
|
|
||||||
def exists(self, profile):
|
def exists(self, profile):
|
||||||
@ -62,12 +62,12 @@ class AppArmorControl(CoreSysAttributes):
|
|||||||
if self.available:
|
if self.available:
|
||||||
await self._reload_service()
|
await self._reload_service()
|
||||||
else:
|
else:
|
||||||
_LOGGER.info("AppArmor is not enabled on Host")
|
_LOGGER.info("AppArmor is not enabled on host")
|
||||||
|
|
||||||
async def load_profile(self, profile_name, profile_file):
|
async def load_profile(self, profile_name, profile_file):
|
||||||
"""Load/Update a new/exists profile into AppArmor."""
|
"""Load/Update a new/exists profile into AppArmor."""
|
||||||
if not validate_profile(profile_name, profile_file):
|
if not validate_profile(profile_name, profile_file):
|
||||||
_LOGGER.error("profile is not valid with name %s", profile_name)
|
_LOGGER.error("Profile is not valid with name %s", profile_name)
|
||||||
raise HostAppArmorError()
|
raise HostAppArmorError()
|
||||||
|
|
||||||
# Copy to AppArmor folder
|
# Copy to AppArmor folder
|
||||||
|
@ -24,7 +24,7 @@ class SystemControl(CoreSysAttributes):
|
|||||||
if flag == HOSTNAME and self.sys_dbus.hostname.is_connected:
|
if flag == HOSTNAME and self.sys_dbus.hostname.is_connected:
|
||||||
return
|
return
|
||||||
|
|
||||||
_LOGGER.error("No %s dbus connection available", flag)
|
_LOGGER.error("No %s D-Bus connection available", flag)
|
||||||
raise HostNotSupportedError()
|
raise HostNotSupportedError()
|
||||||
|
|
||||||
async def reboot(self):
|
async def reboot(self):
|
||||||
@ -51,6 +51,6 @@ class SystemControl(CoreSysAttributes):
|
|||||||
"""Set local a new Hostname."""
|
"""Set local a new Hostname."""
|
||||||
self._check_dbus(HOSTNAME)
|
self._check_dbus(HOSTNAME)
|
||||||
|
|
||||||
_LOGGER.info("Set Hostname %s", hostname)
|
_LOGGER.info("Set hostname %s", hostname)
|
||||||
await self.sys_dbus.hostname.set_static_hostname(hostname)
|
await self.sys_dbus.hostname.set_static_hostname(hostname)
|
||||||
await self.sys_host.info.update()
|
await self.sys_host.info.update()
|
||||||
|
@ -48,7 +48,7 @@ class InfoCenter(CoreSysAttributes):
|
|||||||
async def update(self):
|
async def update(self):
|
||||||
"""Update properties over dbus."""
|
"""Update properties over dbus."""
|
||||||
if not self.sys_dbus.hostname.is_connected:
|
if not self.sys_dbus.hostname.is_connected:
|
||||||
_LOGGER.error("No hostname dbus connection available")
|
_LOGGER.error("No hostname D-Bus connection available")
|
||||||
raise HostNotSupportedError()
|
raise HostNotSupportedError()
|
||||||
|
|
||||||
_LOGGER.info("Update local host information")
|
_LOGGER.info("Update local host information")
|
||||||
|
@ -95,5 +95,5 @@ class ServiceInfo:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def read_from(unit):
|
def read_from(unit):
|
||||||
"""Parse data from dbus into this object."""
|
"""Parse data from D-Bus into this object."""
|
||||||
return ServiceInfo(unit[0], unit[1], unit[3])
|
return ServiceInfo(unit[0], unit[1], unit[3])
|
||||||
|
@ -24,7 +24,7 @@ RE_TTY = re.compile(r"tty[A-Z]+")
|
|||||||
|
|
||||||
|
|
||||||
class Hardware:
|
class Hardware:
|
||||||
"""Represent an interface to procfs, sysfs and udev."""
|
"""Representation of an interface to procfs, sysfs and udev."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Init hardware object."""
|
"""Init hardware object."""
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Schedule for HassIO."""
|
"""Schedule for Hass.io."""
|
||||||
import logging
|
import logging
|
||||||
from datetime import date, datetime, time, timedelta
|
from datetime import date, datetime, time, timedelta
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ TASK = 'task'
|
|||||||
|
|
||||||
|
|
||||||
class Scheduler:
|
class Scheduler:
|
||||||
"""Schedule task inside HassIO."""
|
"""Schedule task inside Hass.io."""
|
||||||
|
|
||||||
def __init__(self, loop):
|
def __init__(self, loop):
|
||||||
"""Initialize task schedule."""
|
"""Initialize task schedule."""
|
||||||
@ -22,18 +22,18 @@ class Scheduler:
|
|||||||
def register_task(self, coro_callback, interval, repeat=True):
|
def register_task(self, coro_callback, interval, repeat=True):
|
||||||
"""Schedule a coroutine.
|
"""Schedule a coroutine.
|
||||||
|
|
||||||
The coroutien need to be a callback without arguments.
|
The coroutine need to be a callback without arguments.
|
||||||
"""
|
"""
|
||||||
task_id = hash(coro_callback)
|
task_id = hash(coro_callback)
|
||||||
|
|
||||||
# generate data
|
# Generate data
|
||||||
opts = {
|
opts = {
|
||||||
CALL: coro_callback,
|
CALL: coro_callback,
|
||||||
INTERVAL: interval,
|
INTERVAL: interval,
|
||||||
REPEAT: repeat,
|
REPEAT: repeat,
|
||||||
}
|
}
|
||||||
|
|
||||||
# schedule task
|
# Schedule task
|
||||||
self._data[task_id] = opts
|
self._data[task_id] = opts
|
||||||
self._schedule_task(interval, task_id)
|
self._schedule_task(interval, task_id)
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ class Scheduler:
|
|||||||
tomorrow = datetime.combine(
|
tomorrow = datetime.combine(
|
||||||
date.today() + timedelta(days=1), interval)
|
date.today() + timedelta(days=1), interval)
|
||||||
|
|
||||||
# check if we run it today or next day
|
# Check if we run it today or next day
|
||||||
if today > datetime.today():
|
if today > datetime.today():
|
||||||
calc = today
|
calc = today
|
||||||
else:
|
else:
|
||||||
@ -68,7 +68,7 @@ class Scheduler:
|
|||||||
|
|
||||||
job = self.loop.call_at(calc.timestamp(), self._run_task, task_id)
|
job = self.loop.call_at(calc.timestamp(), self._run_task, task_id)
|
||||||
else:
|
else:
|
||||||
_LOGGER.fatal("Unknow interval %s (type: %s) for scheduler %s",
|
_LOGGER.fatal("Unknown interval %s (type: %s) for scheduler %s",
|
||||||
interval, type(interval), task_id)
|
interval, type(interval), task_id)
|
||||||
|
|
||||||
# Store job
|
# Store job
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""Handle internal services discovery."""
|
"""Handle internal services discovery."""
|
||||||
|
|
||||||
from .discovery import Discovery # noqa
|
|
||||||
from .mqtt import MQTTService
|
from .mqtt import MQTTService
|
||||||
from .data import ServicesData
|
from .data import ServicesData
|
||||||
from ..const import SERVICE_MQTT
|
from ..const import SERVICE_MQTT
|
||||||
@ -35,10 +33,6 @@ class ServiceManager(CoreSysAttributes):
|
|||||||
for slug, service in AVAILABLE_SERVICES.items():
|
for slug, service in AVAILABLE_SERVICES.items():
|
||||||
self.services_obj[slug] = service(self.coresys)
|
self.services_obj[slug] = service(self.coresys)
|
||||||
|
|
||||||
# Read exists discovery messages
|
|
||||||
self.sys_discovery.load()
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""Reset available data."""
|
"""Reset available data."""
|
||||||
self.data.reset_data()
|
self.data.reset_data()
|
||||||
self.sys_discovery.load()
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Handle service data for persistent supervisor reboot."""
|
"""Handle service data for persistent supervisor reboot."""
|
||||||
|
|
||||||
from .validate import SCHEMA_SERVICES_FILE
|
from .validate import SCHEMA_SERVICES_CONFIG
|
||||||
from ..const import FILE_HASSIO_SERVICES, ATTR_DISCOVERY, SERVICE_MQTT
|
from ..const import FILE_HASSIO_SERVICES, SERVICE_MQTT
|
||||||
from ..utils.json import JsonConfig
|
from ..utils.json import JsonConfig
|
||||||
|
|
||||||
|
|
||||||
@ -10,14 +10,9 @@ class ServicesData(JsonConfig):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize services data."""
|
"""Initialize services data."""
|
||||||
super().__init__(FILE_HASSIO_SERVICES, SCHEMA_SERVICES_FILE)
|
super().__init__(FILE_HASSIO_SERVICES, SCHEMA_SERVICES_CONFIG)
|
||||||
|
|
||||||
@property
|
|
||||||
def discovery(self):
|
|
||||||
"""Return discovery data for home-assistant."""
|
|
||||||
return self._data[ATTR_DISCOVERY]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mqtt(self):
|
def mqtt(self):
|
||||||
"""Return settings for mqtt service."""
|
"""Return settings for MQTT service."""
|
||||||
return self._data[SERVICE_MQTT]
|
return self._data[SERVICE_MQTT]
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
"""Handle discover message for Home-Assistant."""
|
|
||||||
import logging
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from ..const import ATTR_UUID
|
|
||||||
from ..coresys import CoreSysAttributes
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
EVENT_DISCOVERY_ADD = 'hassio_discovery_add'
|
|
||||||
EVENT_DISCOVERY_DEL = 'hassio_discovery_del'
|
|
||||||
|
|
||||||
|
|
||||||
class Discovery(CoreSysAttributes):
|
|
||||||
"""Home-Assistant Discovery handler."""
|
|
||||||
|
|
||||||
def __init__(self, coresys):
|
|
||||||
"""Initialize discovery handler."""
|
|
||||||
self.coresys = coresys
|
|
||||||
self.message_obj = {}
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
"""Load exists discovery message into storage."""
|
|
||||||
messages = {}
|
|
||||||
for message in self._data:
|
|
||||||
discovery = Message(**message)
|
|
||||||
messages[discovery.uuid] = discovery
|
|
||||||
|
|
||||||
self.message_obj = messages
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
"""Write discovery message into data file."""
|
|
||||||
messages = []
|
|
||||||
for message in self.message_obj.values():
|
|
||||||
messages.append(message.raw())
|
|
||||||
|
|
||||||
self._data.clear()
|
|
||||||
self._data.extend(messages)
|
|
||||||
self.sys_services.data.save_data()
|
|
||||||
|
|
||||||
def get(self, uuid):
|
|
||||||
"""Return discovery message."""
|
|
||||||
return self.message_obj.get(uuid)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _data(self):
|
|
||||||
"""Return discovery data."""
|
|
||||||
return self.sys_services.data.discovery
|
|
||||||
|
|
||||||
@property
|
|
||||||
def list_messages(self):
|
|
||||||
"""Return list of available discovery messages."""
|
|
||||||
return self.message_obj.values()
|
|
||||||
|
|
||||||
def send(self, provider, component, platform=None, config=None):
|
|
||||||
"""Send a discovery message to Home-Assistant."""
|
|
||||||
message = Message(provider, component, platform, config)
|
|
||||||
|
|
||||||
# Already exists?
|
|
||||||
for exists_message in self.message_obj:
|
|
||||||
if exists_message == message:
|
|
||||||
_LOGGER.warning("Found douplicate discovery message from %s",
|
|
||||||
provider)
|
|
||||||
return exists_message
|
|
||||||
|
|
||||||
_LOGGER.info("Send discovery to Home-Assistant %s/%s from %s",
|
|
||||||
component, platform, provider)
|
|
||||||
self.message_obj[message.uuid] = message
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
# send event to Home-Assistant
|
|
||||||
self.sys_create_task(self.sys_homeassistant.send_event(
|
|
||||||
EVENT_DISCOVERY_ADD, {ATTR_UUID: message.uuid}))
|
|
||||||
|
|
||||||
return message
|
|
||||||
|
|
||||||
def remove(self, message):
|
|
||||||
"""Remove a discovery message from Home-Assistant."""
|
|
||||||
self.message_obj.pop(message.uuid, None)
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
# send event to Home-Assistant
|
|
||||||
self.sys_create_task(self.sys_homeassistant.send_event(
|
|
||||||
EVENT_DISCOVERY_DEL, {ATTR_UUID: message.uuid}))
|
|
||||||
|
|
||||||
|
|
||||||
class Message:
|
|
||||||
"""Represent a single Discovery message."""
|
|
||||||
|
|
||||||
def __init__(self, provider, component, platform, config, uuid=None):
|
|
||||||
"""Initialize discovery message."""
|
|
||||||
self.provider = provider
|
|
||||||
self.component = component
|
|
||||||
self.platform = platform
|
|
||||||
self.config = config
|
|
||||||
self.uuid = uuid or uuid4().hex
|
|
||||||
|
|
||||||
def raw(self):
|
|
||||||
"""Return raw discovery message."""
|
|
||||||
return self.__dict__
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
"""Compare with other message."""
|
|
||||||
for attribute in ('provider', 'component', 'platform', 'config'):
|
|
||||||
if getattr(self, attribute) != getattr(other, attribute):
|
|
||||||
return False
|
|
||||||
return True
|
|
@ -1,6 +1,7 @@
|
|||||||
"""Interface for single service."""
|
"""Interface for single service."""
|
||||||
|
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..const import PROVIDE_SERVICE
|
||||||
|
|
||||||
|
|
||||||
class ServiceInterface(CoreSysAttributes):
|
class ServiceInterface(CoreSysAttributes):
|
||||||
@ -26,9 +27,13 @@ class ServiceInterface(CoreSysAttributes):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def provider(self):
|
def providers(self):
|
||||||
"""Return name of service provider."""
|
"""Return name of service providers addon."""
|
||||||
return None
|
addons = []
|
||||||
|
for addon in self.sys_addons.list_installed:
|
||||||
|
if addon.services_role.get(self.slug) == PROVIDE_SERVICE:
|
||||||
|
addons.append(addon.slug)
|
||||||
|
return addons
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self):
|
def enabled(self):
|
||||||
@ -45,10 +50,10 @@ class ServiceInterface(CoreSysAttributes):
|
|||||||
return self._data
|
return self._data
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def set_service_data(self, provider, data):
|
def set_service_data(self, addon, data):
|
||||||
"""Write the data into service object."""
|
"""Write the data into service object."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def del_service_data(self, provider):
|
def del_service_data(self, addon):
|
||||||
"""Remove the data from service object."""
|
"""Remove the data from service object."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
"""Provide MQTT Service."""
|
"""Provide the MQTT Service."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .interface import ServiceInterface
|
from .interface import ServiceInterface
|
||||||
from .validate import SCHEMA_SERVICE_MQTT
|
from .validate import SCHEMA_SERVICE_MQTT
|
||||||
from ..const import (
|
from ..const import ATTR_ADDON, SERVICE_MQTT
|
||||||
ATTR_PROVIDER, SERVICE_MQTT, ATTR_HOST, ATTR_PORT, ATTR_USERNAME,
|
from ..exceptions import ServicesError
|
||||||
ATTR_PASSWORD, ATTR_PROTOCOL, ATTR_DISCOVERY_ID)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MQTTService(ServiceInterface):
|
class MQTTService(ServiceInterface):
|
||||||
"""Provide mqtt services."""
|
"""Provide MQTT services."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def slug(self):
|
def slug(self):
|
||||||
@ -28,62 +27,24 @@ class MQTTService(ServiceInterface):
|
|||||||
"""Return data schema of this service."""
|
"""Return data schema of this service."""
|
||||||
return SCHEMA_SERVICE_MQTT
|
return SCHEMA_SERVICE_MQTT
|
||||||
|
|
||||||
@property
|
def set_service_data(self, addon, data):
|
||||||
def provider(self):
|
|
||||||
"""Return name of service provider."""
|
|
||||||
return self._data.get(ATTR_PROVIDER)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def hass_config(self):
|
|
||||||
"""Return Home-Assistant mqtt config."""
|
|
||||||
if not self.enabled:
|
|
||||||
return None
|
|
||||||
|
|
||||||
hass_config = {
|
|
||||||
'host': self._data[ATTR_HOST],
|
|
||||||
'port': self._data[ATTR_PORT],
|
|
||||||
'protocol': self._data[ATTR_PROTOCOL]
|
|
||||||
}
|
|
||||||
if ATTR_USERNAME in self._data:
|
|
||||||
hass_config['user']: self._data[ATTR_USERNAME]
|
|
||||||
if ATTR_PASSWORD in self._data:
|
|
||||||
hass_config['password']: self._data[ATTR_PASSWORD]
|
|
||||||
|
|
||||||
return hass_config
|
|
||||||
|
|
||||||
def set_service_data(self, provider, data):
|
|
||||||
"""Write the data into service object."""
|
"""Write the data into service object."""
|
||||||
if self.enabled:
|
if self.enabled:
|
||||||
_LOGGER.error("It is already a mqtt in use from %s", self.provider)
|
_LOGGER.error(
|
||||||
return False
|
"It is already a MQTT in use from %s", self._data[ATTR_ADDON])
|
||||||
|
raise ServicesError()
|
||||||
|
|
||||||
self._data.update(data)
|
self._data.update(data)
|
||||||
self._data[ATTR_PROVIDER] = provider
|
self._data[ATTR_ADDON] = addon.slug
|
||||||
|
|
||||||
if provider == 'homeassistant':
|
_LOGGER.info("Set %s as service provider for mqtt", addon.slug)
|
||||||
_LOGGER.info("Use mqtt settings from Home-Assistant")
|
|
||||||
self.save()
|
self.save()
|
||||||
return True
|
|
||||||
|
|
||||||
# discover mqtt to homeassistant
|
def del_service_data(self, addon):
|
||||||
message = self.sys_discovery.send(
|
|
||||||
provider, SERVICE_MQTT, None, self.hass_config)
|
|
||||||
|
|
||||||
self._data[ATTR_DISCOVERY_ID] = message.uuid
|
|
||||||
self.save()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def del_service_data(self, provider):
|
|
||||||
"""Remove the data from service object."""
|
"""Remove the data from service object."""
|
||||||
if not self.enabled:
|
if not self.enabled:
|
||||||
_LOGGER.warning("Can't remove not exists services.")
|
_LOGGER.warning("Can't remove not exists services")
|
||||||
return False
|
raise ServicesError()
|
||||||
|
|
||||||
discovery_id = self._data.get(ATTR_DISCOVERY_ID)
|
|
||||||
if discovery_id:
|
|
||||||
self.sys_discovery.remove(
|
|
||||||
self.sys_discovery.get(discovery_id))
|
|
||||||
|
|
||||||
self._data.clear()
|
self._data.clear()
|
||||||
self.save()
|
self.save()
|
||||||
return True
|
|
||||||
|
@ -1,23 +1,11 @@
|
|||||||
"""Validate services schema."""
|
"""Validate services schema."""
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
SERVICE_MQTT, ATTR_HOST, ATTR_PORT, ATTR_PASSWORD, ATTR_USERNAME, ATTR_SSL,
|
SERVICE_MQTT, ATTR_HOST, ATTR_PORT, ATTR_PASSWORD, ATTR_USERNAME, ATTR_SSL,
|
||||||
ATTR_PROVIDER, ATTR_PROTOCOL, ATTR_DISCOVERY, ATTR_COMPONENT, ATTR_UUID,
|
ATTR_ADDON, ATTR_PROTOCOL)
|
||||||
ATTR_PLATFORM, ATTR_CONFIG, ATTR_DISCOVERY_ID)
|
|
||||||
from ..validate import NETWORK_PORT
|
from ..validate import NETWORK_PORT
|
||||||
|
from ..utils.validate import schema_or
|
||||||
|
|
||||||
SCHEMA_DISCOVERY = vol.Schema([
|
|
||||||
vol.Schema({
|
|
||||||
vol.Required(ATTR_UUID): vol.Match(r"^[0-9a-f]{32}$"),
|
|
||||||
vol.Required(ATTR_PROVIDER): vol.Coerce(str),
|
|
||||||
vol.Required(ATTR_COMPONENT): vol.Coerce(str),
|
|
||||||
vol.Required(ATTR_PLATFORM): vol.Any(None, vol.Coerce(str)),
|
|
||||||
vol.Required(ATTR_CONFIG): vol.Any(None, dict),
|
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
@ -33,12 +21,15 @@ SCHEMA_SERVICE_MQTT = vol.Schema({
|
|||||||
|
|
||||||
|
|
||||||
SCHEMA_CONFIG_MQTT = SCHEMA_SERVICE_MQTT.extend({
|
SCHEMA_CONFIG_MQTT = SCHEMA_SERVICE_MQTT.extend({
|
||||||
vol.Required(ATTR_PROVIDER): vol.Coerce(str),
|
vol.Required(ATTR_ADDON): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_DISCOVERY_ID): vol.Match(r"^[0-9a-f]{32}$"),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_SERVICES_FILE = vol.Schema({
|
SCHEMA_SERVICES_CONFIG = vol.Schema({
|
||||||
vol.Optional(SERVICE_MQTT, default=dict): vol.Any({}, SCHEMA_CONFIG_MQTT),
|
vol.Optional(SERVICE_MQTT, default=dict): schema_or(SCHEMA_CONFIG_MQTT),
|
||||||
vol.Optional(ATTR_DISCOVERY, default=list): vol.Any([], SCHEMA_DISCOVERY),
|
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
}, extra=vol.REMOVE_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
DISCOVERY_SERVICES = {
|
||||||
|
SERVICE_MQTT: SCHEMA_SERVICE_MQTT,
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Represent a snapshot file."""
|
"""Representation of a snapshot file."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from base64 import b64decode, b64encode
|
from base64 import b64decode, b64encode
|
||||||
import json
|
import json
|
||||||
@ -29,7 +29,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class Snapshot(CoreSysAttributes):
|
class Snapshot(CoreSysAttributes):
|
||||||
"""A signle hassio snapshot."""
|
"""A single Hass.io snapshot."""
|
||||||
|
|
||||||
def __init__(self, coresys, tar_file):
|
def __init__(self, coresys, tar_file):
|
||||||
"""Initialize a snapshot."""
|
"""Initialize a snapshot."""
|
||||||
@ -72,7 +72,7 @@ class Snapshot(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def addon_list(self):
|
def addon_list(self):
|
||||||
"""Return a list of addons slugs."""
|
"""Return a list of add-ons slugs."""
|
||||||
return [addon_data[ATTR_SLUG] for addon_data in self.addons]
|
return [addon_data[ATTR_SLUG] for addon_data in self.addons]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -92,12 +92,12 @@ class Snapshot(CoreSysAttributes):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def homeassistant_version(self):
|
def homeassistant_version(self):
|
||||||
"""Return snapshot homeassistant version."""
|
"""Return snapshot Home Assistant version."""
|
||||||
return self._data[ATTR_HOMEASSISTANT].get(ATTR_VERSION)
|
return self._data[ATTR_HOMEASSISTANT].get(ATTR_VERSION)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def homeassistant(self):
|
def homeassistant(self):
|
||||||
"""Return snapshot homeassistant data."""
|
"""Return snapshot Home Assistant data."""
|
||||||
return self._data[ATTR_HOMEASSISTANT]
|
return self._data[ATTR_HOMEASSISTANT]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -119,7 +119,7 @@ class Snapshot(CoreSysAttributes):
|
|||||||
|
|
||||||
def new(self, slug, name, date, sys_type, password=None):
|
def new(self, slug, name, date, sys_type, password=None):
|
||||||
"""Initialize a new snapshot."""
|
"""Initialize a new snapshot."""
|
||||||
# init metadata
|
# Init metadata
|
||||||
self._data[ATTR_SLUG] = slug
|
self._data[ATTR_SLUG] = slug
|
||||||
self._data[ATTR_NAME] = name
|
self._data[ATTR_NAME] = name
|
||||||
self._data[ATTR_DATE] = date
|
self._data[ATTR_DATE] = date
|
||||||
@ -306,16 +306,16 @@ class Snapshot(CoreSysAttributes):
|
|||||||
await asyncio.wait(tasks)
|
await asyncio.wait(tasks)
|
||||||
|
|
||||||
async def store_folders(self, folder_list=None):
|
async def store_folders(self, folder_list=None):
|
||||||
"""Backup hassio data into snapshot."""
|
"""Backup Hass.io data into snapshot."""
|
||||||
folder_list = set(folder_list or ALL_FOLDERS)
|
folder_list = set(folder_list or ALL_FOLDERS)
|
||||||
|
|
||||||
def _folder_save(name):
|
def _folder_save(name):
|
||||||
"""Intenal function to snapshot a folder."""
|
"""Internal function to snapshot a folder."""
|
||||||
slug_name = name.replace("/", "_")
|
slug_name = name.replace("/", "_")
|
||||||
tar_name = Path(self._tmp.name, f"{slug_name}.tar.gz")
|
tar_name = Path(self._tmp.name, f"{slug_name}.tar.gz")
|
||||||
origin_dir = Path(self.sys_config.path_hassio, name)
|
origin_dir = Path(self.sys_config.path_hassio, name)
|
||||||
|
|
||||||
# Check if exsits
|
# Check if exists
|
||||||
if not origin_dir.is_dir():
|
if not origin_dir.is_dir():
|
||||||
_LOGGER.warning("Can't find snapshot folder %s", name)
|
_LOGGER.warning("Can't find snapshot folder %s", name)
|
||||||
return
|
return
|
||||||
@ -338,7 +338,7 @@ class Snapshot(CoreSysAttributes):
|
|||||||
await asyncio.wait(tasks)
|
await asyncio.wait(tasks)
|
||||||
|
|
||||||
async def restore_folders(self, folder_list=None):
|
async def restore_folders(self, folder_list=None):
|
||||||
"""Backup hassio data into snapshot."""
|
"""Backup Hass.io data into snapshot."""
|
||||||
folder_list = set(folder_list or self.folders)
|
folder_list = set(folder_list or self.folders)
|
||||||
|
|
||||||
def _folder_restore(name):
|
def _folder_restore(name):
|
||||||
@ -356,7 +356,7 @@ class Snapshot(CoreSysAttributes):
|
|||||||
if origin_dir.is_dir():
|
if origin_dir.is_dir():
|
||||||
remove_folder(origin_dir)
|
remove_folder(origin_dir)
|
||||||
|
|
||||||
# Performe a restore
|
# Perform a restore
|
||||||
try:
|
try:
|
||||||
_LOGGER.info("Restore folder %s", name)
|
_LOGGER.info("Restore folder %s", name)
|
||||||
with SecureTarFile(tar_name, 'r', key=self._key) as tar_file:
|
with SecureTarFile(tar_name, 'r', key=self._key) as tar_file:
|
||||||
@ -372,7 +372,7 @@ class Snapshot(CoreSysAttributes):
|
|||||||
await asyncio.wait(tasks)
|
await asyncio.wait(tasks)
|
||||||
|
|
||||||
def store_homeassistant(self):
|
def store_homeassistant(self):
|
||||||
"""Read all data from homeassistant object."""
|
"""Read all data from Home Assistant object."""
|
||||||
self.homeassistant[ATTR_VERSION] = self.sys_homeassistant.version
|
self.homeassistant[ATTR_VERSION] = self.sys_homeassistant.version
|
||||||
self.homeassistant[ATTR_WATCHDOG] = self.sys_homeassistant.watchdog
|
self.homeassistant[ATTR_WATCHDOG] = self.sys_homeassistant.watchdog
|
||||||
self.homeassistant[ATTR_BOOT] = self.sys_homeassistant.boot
|
self.homeassistant[ATTR_BOOT] = self.sys_homeassistant.boot
|
||||||
@ -393,7 +393,7 @@ class Snapshot(CoreSysAttributes):
|
|||||||
self._encrypt_data(self.sys_homeassistant.api_password)
|
self._encrypt_data(self.sys_homeassistant.api_password)
|
||||||
|
|
||||||
def restore_homeassistant(self):
|
def restore_homeassistant(self):
|
||||||
"""Write all data to homeassistant object."""
|
"""Write all data to the Home Assistant object."""
|
||||||
self.sys_homeassistant.watchdog = self.homeassistant[ATTR_WATCHDOG]
|
self.sys_homeassistant.watchdog = self.homeassistant[ATTR_WATCHDOG]
|
||||||
self.sys_homeassistant.boot = self.homeassistant[ATTR_BOOT]
|
self.sys_homeassistant.boot = self.homeassistant[ATTR_BOOT]
|
||||||
self.sys_homeassistant.wait_boot = self.homeassistant[ATTR_WAIT_BOOT]
|
self.sys_homeassistant.wait_boot = self.homeassistant[ATTR_WAIT_BOOT]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Util addons functions."""
|
"""Util add-on functions."""
|
||||||
import hashlib
|
import hashlib
|
||||||
import shutil
|
import shutil
|
||||||
import re
|
import re
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""Validate some things around restore."""
|
"""Validate some things around restore."""
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
|
28
hassio/utils/validate.py
Normal file
28
hassio/utils/validate.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"""Validate utils."""
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
|
||||||
|
def schema_or(schema):
|
||||||
|
"""Allow schema or empty."""
|
||||||
|
def _wrapper(value):
|
||||||
|
"""Wrapper for validator."""
|
||||||
|
if not value:
|
||||||
|
return value
|
||||||
|
return schema(value)
|
||||||
|
|
||||||
|
return _wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def validate_timezone(timezone):
|
||||||
|
"""Validate voluptuous timezone."""
|
||||||
|
try:
|
||||||
|
pytz.timezone(timezone)
|
||||||
|
except pytz.exceptions.UnknownTimeZoneError:
|
||||||
|
raise vol.Invalid(
|
||||||
|
"Invalid time zone passed in. Valid options can be found here: "
|
||||||
|
"http://en.wikipedia.org/wiki/List_of_tz_database_time_zones") \
|
||||||
|
from None
|
||||||
|
|
||||||
|
return timezone
|
@ -3,15 +3,17 @@ import uuid
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
import pytz
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_CHANNEL, ATTR_TIMEZONE, ATTR_HASSOS,
|
ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_CHANNEL, ATTR_TIMEZONE, ATTR_HASSOS,
|
||||||
ATTR_ADDONS_CUSTOM_LIST, ATTR_PASSWORD, ATTR_HOMEASSISTANT, ATTR_HASSIO,
|
ATTR_ADDONS_CUSTOM_LIST, ATTR_PASSWORD, ATTR_HOMEASSISTANT, ATTR_HASSIO,
|
||||||
ATTR_BOOT, ATTR_LAST_BOOT, ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG,
|
ATTR_BOOT, ATTR_LAST_BOOT, ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, ATTR_CONFIG,
|
||||||
ATTR_WAIT_BOOT, ATTR_UUID, ATTR_REFRESH_TOKEN, ATTR_HASSOS_CLI,
|
ATTR_WAIT_BOOT, ATTR_UUID, ATTR_REFRESH_TOKEN, ATTR_HASSOS_CLI,
|
||||||
ATTR_ACCESS_TOKEN,
|
ATTR_ACCESS_TOKEN, ATTR_DISCOVERY, ATTR_ADDON, ATTR_COMPONENT,
|
||||||
|
ATTR_PLATFORM, ATTR_SERVICE,
|
||||||
|
SERVICE_MQTT,
|
||||||
CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV)
|
CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV)
|
||||||
|
from .utils.validate import schema_or, validate_timezone
|
||||||
|
|
||||||
|
|
||||||
RE_REPOSITORY = re.compile(r"^(?P<url>[^#]+)(?:#(?P<branch>[\w\-]+))?$")
|
RE_REPOSITORY = re.compile(r"^(?P<url>[^#]+)(?:#(?P<branch>[\w\-]+))?$")
|
||||||
@ -21,6 +23,8 @@ WAIT_BOOT = vol.All(vol.Coerce(int), vol.Range(min=1, max=60))
|
|||||||
DOCKER_IMAGE = vol.Match(r"^[\w{}]+/[\-\w{}]+$")
|
DOCKER_IMAGE = vol.Match(r"^[\w{}]+/[\-\w{}]+$")
|
||||||
ALSA_DEVICE = vol.Maybe(vol.Match(r"\d+,\d+"))
|
ALSA_DEVICE = vol.Maybe(vol.Match(r"\d+,\d+"))
|
||||||
CHANNELS = vol.In([CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV])
|
CHANNELS = vol.In([CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV])
|
||||||
|
UUID_MATCH = vol.Match(r"^[0-9a-f]{32}$")
|
||||||
|
SERVICE_ALL = vol.In([SERVICE_MQTT])
|
||||||
|
|
||||||
|
|
||||||
def validate_repository(repository):
|
def validate_repository(repository):
|
||||||
@ -40,19 +44,6 @@ def validate_repository(repository):
|
|||||||
REPOSITORIES = vol.All([validate_repository], vol.Unique())
|
REPOSITORIES = vol.All([validate_repository], vol.Unique())
|
||||||
|
|
||||||
|
|
||||||
def validate_timezone(timezone):
|
|
||||||
"""Validate voluptuous timezone."""
|
|
||||||
try:
|
|
||||||
pytz.timezone(timezone)
|
|
||||||
except pytz.exceptions.UnknownTimeZoneError:
|
|
||||||
raise vol.Invalid(
|
|
||||||
"Invalid time zone passed in. Valid options can be found here: "
|
|
||||||
"http://en.wikipedia.org/wiki/List_of_tz_database_time_zones") \
|
|
||||||
from None
|
|
||||||
|
|
||||||
return timezone
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=inconsistent-return-statements
|
# pylint: disable=inconsistent-return-statements
|
||||||
def convert_to_docker_ports(data):
|
def convert_to_docker_ports(data):
|
||||||
"""Convert data into Docker port list."""
|
"""Convert data into Docker port list."""
|
||||||
@ -83,8 +74,7 @@ DOCKER_PORTS = vol.Schema({
|
|||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_HASS_CONFIG = vol.Schema({
|
SCHEMA_HASS_CONFIG = vol.Schema({
|
||||||
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
|
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex): UUID_MATCH,
|
||||||
vol.Match(r"^[0-9a-f]{32}$"),
|
|
||||||
vol.Optional(ATTR_ACCESS_TOKEN): vol.Match(r"^[0-9a-f]{64}$"),
|
vol.Optional(ATTR_ACCESS_TOKEN): vol.Match(r"^[0-9a-f]{64}$"),
|
||||||
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
|
||||||
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): DOCKER_IMAGE,
|
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): DOCKER_IMAGE,
|
||||||
@ -117,3 +107,19 @@ SCHEMA_HASSIO_CONFIG = vol.Schema({
|
|||||||
]): REPOSITORIES,
|
]): REPOSITORIES,
|
||||||
vol.Optional(ATTR_WAIT_BOOT, default=5): WAIT_BOOT,
|
vol.Optional(ATTR_WAIT_BOOT, default=5): WAIT_BOOT,
|
||||||
}, extra=vol.REMOVE_EXTRA)
|
}, extra=vol.REMOVE_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA_DISCOVERY = vol.Schema([
|
||||||
|
vol.Schema({
|
||||||
|
vol.Required(ATTR_UUID): UUID_MATCH,
|
||||||
|
vol.Required(ATTR_ADDON): vol.Coerce(str),
|
||||||
|
vol.Required(ATTR_SERVICE): SERVICE_ALL,
|
||||||
|
vol.Required(ATTR_COMPONENT): vol.Coerce(str),
|
||||||
|
vol.Required(ATTR_PLATFORM): vol.Maybe(vol.Coerce(str)),
|
||||||
|
vol.Required(ATTR_CONFIG): vol.Maybe(dict),
|
||||||
|
}, extra=vol.REMOVE_EXTRA)
|
||||||
|
])
|
||||||
|
|
||||||
|
SCHEMA_DISCOVERY_CONFIG = vol.Schema({
|
||||||
|
vol.Optional(ATTR_DISCOVERY, default=list): schema_or(SCHEMA_DISCOVERY),
|
||||||
|
}, extra=vol.REMOVE_EXTRA)
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit d71a80c4f8b50bfe43f62c991e41b2f97f1ae9b0
|
Subproject commit f11ca53282541326279cec0a4d3a824db45bcecf
|
Loading…
x
Reference in New Issue
Block a user