Merge pull request #728 from home-assistant/dev

Release 132
This commit is contained in:
Pascal Vizeli 2018-09-30 20:16:08 +02:00 committed by GitHub
commit 74e03a9a2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
95 changed files with 1624 additions and 1225 deletions

3
.gitignore vendored
View File

@ -90,3 +90,6 @@ ENV/
# pylint
.pylint.d/
# VS Code
.vscode/

44
API.md
View File

@ -428,11 +428,11 @@ Get all available addons.
"name": "xy bla",
"slug": "xy",
"description": "description",
"arch": ["armhf", "aarch64", "i386", "amd64"],
"repository": "core|local|REP_ID",
"version": "LAST_VERSION",
"installed": "none|INSTALL_VERSION",
"detached": "bool",
"available": "bool",
"build": "bool",
"url": "null|url",
"icon": "bool",
@ -463,6 +463,9 @@ Get all available addons.
"auto_update": "bool",
"url": "null|url of addon",
"detached": "bool",
"available": "bool",
"arch": ["armhf", "aarch64", "i386", "amd64"],
"machine": "[raspberrypi2, tinker]",
"repository": "12345678|null",
"version": "null|VERSION_INSTALLED",
"last_version": "LAST_VERSION",
@ -496,8 +499,8 @@ Get all available addons.
"audio": "bool",
"audio_input": "null|0,0",
"audio_output": "null|0,0",
"services": "null|['mqtt']",
"discovery": "null|['component/platform']"
"services_role": "['service:access']",
"discovery": "['service']"
}
```
@ -573,12 +576,13 @@ Write data to add-on stdin
### Service discovery
- GET `/services/discovery`
- GET `/discovery`
```json
{
"discovery": [
{
"provider": "name",
"addon": "slug",
"service": "name",
"uuid": "uuid",
"component": "component",
"platform": "null|platform",
@ -588,10 +592,11 @@ Write data to add-on stdin
}
```
- GET `/services/discovery/{UUID}`
- GET `/discovery/{UUID}`
```json
{
"provider": "name",
"addon": "slug",
"service": "name",
"uuid": "uuid",
"component": "component",
"platform": "null|platform",
@ -599,9 +604,10 @@ Write data to add-on stdin
}
```
- POST `/services/discovery`
- POST `/discovery`
```json
{
"service": "name",
"component": "component",
"platform": "null|platform",
"config": {}
@ -615,7 +621,7 @@ return:
}
```
- DEL `/services/discovery/{UUID}`
- DEL `/discovery/{UUID}`
- GET `/services`
```json
@ -624,7 +630,7 @@ return:
{
"slug": "name",
"available": "bool",
"provider": "null|name|list"
"providers": "list"
}
]
}
@ -632,12 +638,10 @@ return:
#### MQTT
This service performs an auto discovery to Home-Assistant.
- GET `/services/mqtt`
```json
{
"provider": "name",
"addon": "name",
"host": "xy",
"port": "8883",
"ssl": "bool",
@ -660,3 +664,17 @@ This service performs an auto discovery to Home-Assistant.
```
- DEL `/services/mqtt`
### Misc
- GET `/version`
```json
{
"supervisor": "version",
"homeassistant": "version",
"hassos": "null|version",
"machine": "type",
"arch": "arch",
"channel": "stable|beta|dev"
}
```

View File

@ -1,4 +1,4 @@
"""Init file for HassIO addons."""
"""Init file for Hass.io add-ons."""
import asyncio
import logging
@ -14,10 +14,10 @@ BUILTIN_REPOSITORIES = set((REPOSITORY_CORE, REPOSITORY_LOCAL))
class AddonManager(CoreSysAttributes):
"""Manage addons inside HassIO."""
"""Manage add-ons inside Hass.io."""
def __init__(self, coresys):
"""Initialize docker base wrapper."""
"""Initialize Docker base wrapper."""
self.coresys = coresys
self.data = AddonsData(coresys)
self.addons_obj = {}
@ -25,51 +25,44 @@ class AddonManager(CoreSysAttributes):
@property
def list_addons(self):
"""Return a list of all addons."""
"""Return a list of all add-ons."""
return list(self.addons_obj.values())
@property
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()
if addon.is_installed]
@property
def list_repositories(self):
"""Return list of addon repositories."""
"""Return list of add-on repositories."""
return list(self.repositories_obj.values())
def get(self, addon_slug):
"""Return an add-on from 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):
"""Return an add-on from hassio token."""
"""Return an add-on from Hass.io token."""
for addon in self.list_addons:
if addon.is_installed and token == addon.hassio_token:
return addon
return None
async def load(self):
"""Startup addon management."""
"""Start up add-on management."""
self.data.reload()
# init hassio built-in repositories
# Init Hass.io built-in repositories
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)
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
self.repositories_obj.values()]
if tasks:
@ -113,14 +106,14 @@ class AddonManager(CoreSysAttributes):
await self.load_addons()
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)
# calc diff
add_addons = all_addons - set(self.addons_obj)
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))
# new addons
@ -139,14 +132,14 @@ class AddonManager(CoreSysAttributes):
self.addons_obj.pop(addon_slug)
async def boot(self, stage):
"""Boot addons with mode auto."""
"""Boot add-ons with mode auto."""
tasks = []
for addon in self.addons_obj.values():
if addon.is_installed and addon.boot == BOOT_AUTO and \
addon.startup == stage:
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:
await asyncio.wait(tasks)
await asyncio.sleep(self.sys_config.wait_boot)
@ -160,6 +153,6 @@ class AddonManager(CoreSysAttributes):
addon.startup == stage:
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:
await asyncio.wait(tasks)

View File

@ -1,4 +1,4 @@
"""Init file for HassIO addons."""
"""Init file for Hass.io add-ons."""
from contextlib import suppress
from copy import deepcopy
import logging
@ -13,7 +13,8 @@ import voluptuous as vol
from voluptuous.humanize import humanize_error
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 ..const import (
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_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_FULL_ACCESS,
ATTR_PROTECTED, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE,
ATTR_MACHINE,
SECURITY_PROFILE, SECURITY_DISABLE, SECURITY_DEFAULT)
from ..coresys import CoreSysAttributes
from ..docker.addon import DockerAddon
@ -43,7 +45,7 @@ RE_WEBUI = re.compile(
class Addon(CoreSysAttributes):
"""Hold data for addon inside HassIO."""
"""Hold data for add-on inside Hass.io."""
def __init__(self, coresys, slug):
"""Initialize data holder."""
@ -59,29 +61,38 @@ class Addon(CoreSysAttributes):
@property
def slug(self):
"""Return slug/id of addon."""
"""Return slug/id of add-on."""
return self._id
@property
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))
@property
def _data(self):
"""Return addons data storage."""
"""Return add-ons data storage."""
return self.sys_addons.data
@property
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
@property
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
@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
def version_installed(self):
"""Return installed version."""
@ -97,19 +108,19 @@ class Addon(CoreSysAttributes):
self._data.save_data()
def _set_uninstall(self):
"""Set addon as uninstalled."""
"""Set add-on as uninstalled."""
self._data.system.pop(self._id, None)
self._data.user.pop(self._id, None)
self._data.save_data()
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.user[self._id][ATTR_VERSION] = version
self._data.save_data()
def _restore_data(self, user, system):
"""Restore data to addon."""
"""Restore data to add-on."""
self._data.user[self._id] = deepcopy(user)
self._data.system[self._id] = deepcopy(system)
self._data.save_data()
@ -126,7 +137,7 @@ class Addon(CoreSysAttributes):
@options.setter
def options(self, value):
"""Store user addon options."""
"""Store user add-on options."""
if value is None:
self._data.user[self._id][ATTR_OPTIONS] = {}
else:
@ -158,7 +169,7 @@ class Addon(CoreSysAttributes):
@property
def name(self):
"""Return name of addon."""
"""Return name of add-on."""
return self._mesh[ATTR_NAME]
@property
@ -175,14 +186,14 @@ class Addon(CoreSysAttributes):
@property
def hassio_token(self):
"""Return access token for hass.io API."""
"""Return access token for Hass.io API."""
if self.is_installed:
return self._data.user[self._id].get(ATTR_ACCESS_TOKEN)
return None
@property
def description(self):
"""Return description of addon."""
"""Return description of add-on."""
return self._mesh[ATTR_DESCRIPTON]
@property
@ -200,56 +211,55 @@ class Addon(CoreSysAttributes):
@property
def repository(self):
"""Return repository of addon."""
"""Return repository of add-on."""
return self._mesh[ATTR_REPOSITORY]
@property
def last_version(self):
"""Return version of addon."""
"""Return version of add-on."""
if self._id in self._data.cache:
return self._data.cache[self._id][ATTR_VERSION]
return self.version_installed
@property
def protected(self):
"""Return if addon is in protected mode."""
"""Return if add-on is in protected mode."""
if self.is_installed:
return self._data.user[self._id][ATTR_PROTECTED]
return True
@protected.setter
def protected(self, value):
"""Set addon in protected mode."""
"""Set add-on in protected mode."""
self._data.user[self._id][ATTR_PROTECTED] = value
@property
def startup(self):
"""Return startup type of addon."""
"""Return startup type of add-on."""
return self._mesh.get(ATTR_STARTUP)
@property
def services(self):
def services_role(self):
"""Return dict of services with rights."""
raw_services = self._mesh.get(ATTR_SERVICES)
if not raw_services:
return None
return {}
formated_services = {}
services = {}
for data in raw_services:
service = RE_SERVICE.match(data)
formated_services[service.group('service')] = \
service.group('rights') or 'ro'
services[service.group('service')] = service.group('rights')
return formated_services
return services
@property
def discovery(self):
"""Return list of discoverable components/platforms."""
return self._mesh.get(ATTR_DISCOVERY)
return self._mesh.get(ATTR_DISCOVERY, [])
@property
def ports(self):
"""Return ports of addon."""
"""Return ports of add-on."""
if self.host_network or ATTR_PORTS not in self._mesh:
return None
@ -260,7 +270,7 @@ class Addon(CoreSysAttributes):
@ports.setter
def ports(self, value):
"""Set custom ports of addon."""
"""Set custom ports of add-on."""
if value is None:
self._data.user[self._id].pop(ATTR_NETWORK, None)
else:
@ -304,42 +314,42 @@ class Addon(CoreSysAttributes):
@property
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]
@property
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]
@property
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]
@property
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]
@property
def devices(self):
"""Return devices of addon."""
"""Return devices of add-on."""
return self._mesh.get(ATTR_DEVICES)
@property
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)
@property
def tmpfs(self):
"""Return tmpfs of addon."""
"""Return tmpfs of add-on."""
return self._mesh.get(ATTR_TMPFS)
@property
def environment(self):
"""Return environment of addon."""
"""Return environment of add-on."""
return self._mesh.get(ATTR_ENVIRONMENT)
@property
@ -349,7 +359,7 @@ class Addon(CoreSysAttributes):
@property
def apparmor(self):
"""Return True if apparmor is enabled."""
"""Return True if AppArmor is enabled."""
if not self._mesh.get(ATTR_APPARMOR):
return SECURITY_DISABLE
elif self.sys_host.apparmor.exists(self.slug):
@ -358,22 +368,22 @@ class Addon(CoreSysAttributes):
@property
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)
@property
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)
@property
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]
@property
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]
@property
@ -388,7 +398,7 @@ class Addon(CoreSysAttributes):
@property
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]
@property
@ -445,7 +455,7 @@ class Addon(CoreSysAttributes):
@property
def url(self):
"""Return url of addon."""
"""Return URL of add-on."""
return self._mesh.get(ATTR_URL)
@property
@ -468,12 +478,17 @@ class Addon(CoreSysAttributes):
"""Return list of supported 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
def image(self):
"""Return image name of addon."""
"""Return image name of add-on."""
addon_data = self._mesh
# Repository with dockerhub images
# Repository with Dockerhub images
if ATTR_IMAGE in addon_data:
return addon_data[ATTR_IMAGE].format(arch=self.sys_arch)
@ -484,12 +499,12 @@ class Addon(CoreSysAttributes):
@property
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
@property
def map_volumes(self):
"""Return a dict of {volume: policy} from addon."""
"""Return a dict of {volume: policy} from add-on."""
volumes = {}
for volume in self._mesh[ATTR_MAP]:
result = RE_VOLUME.match(volume)
@ -499,37 +514,37 @@ class Addon(CoreSysAttributes):
@property
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)
@property
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)
@property
def path_options(self):
"""Return path to addons options."""
"""Return path to add-on options."""
return Path(self.path_data, "options.json")
@property
def path_location(self):
"""Return path to this addon."""
"""Return path to this add-on."""
return Path(self._mesh[ATTR_LOCATON])
@property
def path_icon(self):
"""Return path to addon icon."""
"""Return path to add-on icon."""
return Path(self.path_location, 'icon.png')
@property
def path_logo(self):
"""Return path to addon logo."""
"""Return path to add-on logo."""
return Path(self.path_location, 'logo.png')
@property
def path_changelog(self):
"""Return path to addon changelog."""
"""Return path to add-on changelog."""
return Path(self.path_location, 'CHANGELOG.md')
@property
@ -544,15 +559,15 @@ class Addon(CoreSysAttributes):
@property
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")
def save_data(self):
"""Save data of addon."""
"""Save data of add-on."""
self.sys_addons.data.save_data()
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
options = self.options
@ -560,15 +575,22 @@ class Addon(CoreSysAttributes):
schema(options)
write_json_file(self.path_options, options)
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))
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:
return True
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):
"""Write asound config to file and return True on success."""
asound_config = self.sys_host.alsa.asound(
@ -578,7 +600,7 @@ class Addon(CoreSysAttributes):
with self.path_asound.open('w') as config_file:
config_file.write(asound_config)
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 True
@ -606,7 +628,7 @@ class Addon(CoreSysAttributes):
@property
def schema(self):
"""Create a schema for addon options."""
"""Create a schema for add-on options."""
raw_schema = self._mesh[ATTR_SCHEMA]
if isinstance(raw_schema, bool):
@ -614,7 +636,7 @@ class Addon(CoreSysAttributes):
return vol.Schema(vol.All(dict, validate_options(raw_schema)))
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:
return True
@ -644,19 +666,19 @@ class Addon(CoreSysAttributes):
return True
async def install(self):
"""Install an addon."""
if self.sys_arch not in self.supported_arch:
"""Install an add-on."""
if not self.available:
_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
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
if not self.path_data.is_dir():
_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()
# Setup/Fix AppArmor profile
@ -670,13 +692,13 @@ class Addon(CoreSysAttributes):
@check_installed
async def uninstall(self):
"""Remove an addon."""
"""Remove an add-on."""
if not await self.instance.remove():
return False
if self.path_data.is_dir():
_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)
# Cleanup audio settings
@ -684,16 +706,19 @@ class Addon(CoreSysAttributes):
with suppress(OSError):
self.path_asound.unlink()
# Cleanup apparmor profile
# Cleanup AppArmor profile
if self.sys_host.apparmor.exists(self.slug):
with suppress(HostAppArmorError):
await self.sys_host.apparmor.remove_profile(self.slug)
# Remove discovery messages
self.remove_discovery()
self._set_uninstall()
return True
async def state(self):
"""Return running state of addon."""
"""Return running state of add-on."""
if not self.is_installed:
return STATE_NONE
@ -703,9 +728,9 @@ class Addon(CoreSysAttributes):
@check_installed
async def start(self):
"""Set options and start addon."""
"""Set options and start add-on."""
if await self.instance.is_running():
_LOGGER.warning("%s allready running!", self.slug)
_LOGGER.warning("%s already running!", self.slug)
return
# Access Token
@ -724,7 +749,7 @@ class Addon(CoreSysAttributes):
@check_installed
def stop(self):
"""Stop addon.
"""Stop add-on.
Return a coroutine.
"""
@ -732,11 +757,11 @@ class Addon(CoreSysAttributes):
@check_installed
async def update(self):
"""Update addon."""
"""Update add-on."""
last_state = await self.state()
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
if not await self.instance.update(self.last_version):
@ -753,13 +778,13 @@ class Addon(CoreSysAttributes):
@check_installed
async def restart(self):
"""Restart addon."""
"""Restart add-on."""
await self.stop()
return await self.start()
@check_installed
def logs(self):
"""Return addons log output.
"""Return add-ons log output.
Return a coroutine.
"""
@ -775,11 +800,11 @@ class Addon(CoreSysAttributes):
@check_installed
async def rebuild(self):
"""Performe a rebuild of local build addon."""
"""Perform a rebuild of local build add-on."""
last_state = await self.state()
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
# remove docker container but not addon config
@ -808,7 +833,7 @@ class Addon(CoreSysAttributes):
@check_installed
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:
# store local image
if self.need_build and not await \
@ -822,7 +847,7 @@ class Addon(CoreSysAttributes):
ATTR_STATE: await self.state(),
}
# store local configs/state
# Store local configs/state
try:
write_json_file(Path(temp, 'addon.json'), data)
except (OSError, json.JSONDecodeError) as err:
@ -846,7 +871,7 @@ class Addon(CoreSysAttributes):
snapshot.add(self.path_data, arcname="data")
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)
except (tarfile.TarError, OSError) as err:
_LOGGER.error("Can't write tarfile %s: %s", tar_file, err)
@ -856,7 +881,7 @@ class Addon(CoreSysAttributes):
return True
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:
# extract snapshot
def _extract_tarfile():
@ -870,13 +895,13 @@ class Addon(CoreSysAttributes):
_LOGGER.error("Can't read tarfile %s: %s", tar_file, err)
return False
# read snapshot data
# Read snapshot data
try:
data = read_json_file(Path(temp, 'addon.json'))
except (OSError, json.JSONDecodeError) as err:
_LOGGER.error("Can't read addon.json: %s", err)
# validate
# Validate
try:
data = SCHEMA_ADDON_SNAPSHOT(data)
except vol.Invalid as err:
@ -884,11 +909,11 @@ class Addon(CoreSysAttributes):
self._id, humanize_error(data, err))
return False
# restore data / reload addon
# Restore data or reload add-on
_LOGGER.info("Restore config for addon %s", self._id)
self._restore_data(data[ATTR_USER], data[ATTR_SYSTEM])
# check version / restore image
# Check version / restore image
version = data[ATTR_VERSION]
if not await self.instance.exists():
_LOGGER.info("Restore image for addon %s", self._id)
@ -902,7 +927,7 @@ class Addon(CoreSysAttributes):
else:
await self.instance.stop()
# restore data
# Restore data
def _restore_data():
"""Restore 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")
return False
# run addon
# Run add-on
if data[ATTR_STATE] == STATE_STARTED:
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

View File

@ -1,4 +1,4 @@
"""HassIO addons build environment."""
"""Hass.io add-on build environment."""
from pathlib import Path
from .validate import SCHEMA_BUILD_CONFIG, BASE_IMAGE
@ -8,10 +8,10 @@ from ..utils.json import JsonConfig
class AddonBuild(JsonConfig, CoreSysAttributes):
"""Handle build options for addons."""
"""Handle build options for add-ons."""
def __init__(self, coresys, slug):
"""Initialize addon builder."""
"""Initialize Hass.io add-on builder."""
self.coresys = coresys
self._id = slug
@ -24,12 +24,12 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
@property
def addon(self):
"""Return addon of build data."""
"""Return add-on of build data."""
return self.sys_addons.get(self._id)
@property
def base_image(self):
"""Base images for this addon."""
"""Base images for this add-on."""
return self._data[ATTR_BUILD_FROM].get(
self.sys_arch, BASE_IMAGE[self.sys_arch])
@ -40,11 +40,11 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
@property
def additional_args(self):
"""Return additional docker build arguments."""
"""Return additional Docker build arguments."""
return self._data[ATTR_ARGS]
def get_docker_args(self, version):
"""Create a dict with docker build arguments."""
"""Create a dict with Docker build arguments."""
args = {
'path': str(self.addon.path_location),
'tag': f"{self.addon.image}:{version}",

View File

@ -1,4 +1,4 @@
"""Init file for HassIO addons."""
"""Init file for Hass.io add-on data."""
import logging
import json
from pathlib import Path
@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
class AddonsData(JsonConfig, CoreSysAttributes):
"""Hold data for addons inside HassIO."""
"""Hold data for Add-ons inside Hass.io."""
def __init__(self, coresys):
"""Initialize data holder."""
@ -30,26 +30,26 @@ class AddonsData(JsonConfig, CoreSysAttributes):
@property
def user(self):
"""Return local addon user data."""
"""Return local add-on user data."""
return self._data[ATTR_USER]
@property
def system(self):
"""Return local addon data."""
"""Return local add-on data."""
return self._data[ATTR_SYSTEM]
@property
def cache(self):
"""Return addon data from cache/repositories."""
"""Return add-on data from cache/repositories."""
return self._cache
@property
def repositories(self):
"""Return addon data from repositories."""
"""Return add-on data from repositories."""
return self._repositories
def reload(self):
"""Read data from addons repository."""
"""Read data from add-on repository."""
self._cache = {}
self._repositories = {}
@ -94,7 +94,7 @@ class AddonsData(JsonConfig, CoreSysAttributes):
self._read_addons_folder(path, slug)
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"):
try:
addon_config = read_json_file(addon)

View File

@ -1,4 +1,4 @@
"""Init file for HassIO addons git."""
"""Init file for Hass.io add-on Git."""
import asyncio
import logging
import functools as ft
@ -16,10 +16,10 @@ _LOGGER = logging.getLogger(__name__)
class GitRepo(CoreSysAttributes):
"""Manage addons git repo."""
"""Manage Add-on Git repository."""
def __init__(self, coresys, path, url):
"""Initialize git base wrapper."""
"""Initialize Git base wrapper."""
self.coresys = coresys
self.repo = None
self.path = path
@ -38,13 +38,13 @@ class GitRepo(CoreSysAttributes):
return self._data[ATTR_BRANCH]
async def load(self):
"""Init git addon repo."""
"""Init Git add-on repository."""
if not self.path.is_dir():
return await self.clone()
async with self.lock:
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(
git.Repo, str(self.path))
@ -57,7 +57,7 @@ class GitRepo(CoreSysAttributes):
return True
async def clone(self):
"""Clone git addon repo."""
"""Clone git add-on repository."""
async with self.lock:
git_args = {
attribute: value
@ -70,7 +70,7 @@ class GitRepo(CoreSysAttributes):
}
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(
git.Repo.clone_from, self.url, str(self.path),
**git_args
@ -78,20 +78,20 @@ class GitRepo(CoreSysAttributes):
except (git.InvalidGitRepositoryError, git.NoSuchPathError,
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()
return False
return True
async def pull(self):
"""Pull git addon repo."""
"""Pull Git add-on repo."""
if self.lock.locked():
_LOGGER.warning("It is already a task in progress.")
_LOGGER.warning("It is already a task in progress")
return False
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
try:
@ -130,19 +130,19 @@ class GitRepo(CoreSysAttributes):
class GitRepoHassIO(GitRepo):
"""HassIO addons repository."""
"""Hass.io add-ons repository."""
def __init__(self, coresys):
"""Initialize git hassio addon repository."""
"""Initialize Git Hass.io add-on repository."""
super().__init__(
coresys, coresys.config.path_addons_core, URL_HASSIO_ADDONS)
class GitRepoCustom(GitRepo):
"""Custom addons repository."""
"""Custom add-ons repository."""
def __init__(self, coresys, url):
"""Initialize git hassio addon repository."""
"""Initialize custom Git Hass.io addo-n repository."""
path = Path(
coresys.config.path_addons_git,
get_hash_from_repository(url))
@ -151,5 +151,5 @@ class GitRepoCustom(GitRepo):
def remove(self):
"""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()

View File

@ -1,15 +1,16 @@
"""Represent a HassIO repository."""
"""Represent a Hass.io repository."""
from .git import GitRepoHassIO, GitRepoCustom
from .utils import get_hash_from_repository
from ..const import (
REPOSITORY_CORE, REPOSITORY_LOCAL, ATTR_NAME, ATTR_URL, ATTR_MAINTAINER)
from ..coresys import CoreSysAttributes
from ..exceptions import APIError
UNKNOWN = 'unknown'
class Repository(CoreSysAttributes):
"""Repository in HassIO."""
"""Repository in Hass.io."""
def __init__(self, coresys, repository):
"""Initialize repository object."""
@ -44,7 +45,7 @@ class Repository(CoreSysAttributes):
@property
def url(self):
"""Return url of repository."""
"""Return URL of repository."""
return self._mesh.get(ATTR_URL, self.source)
@property
@ -59,14 +60,14 @@ class Repository(CoreSysAttributes):
return True
async def update(self):
"""Update addon repository."""
"""Update add-on repository."""
if self.git:
return await self.git.pull()
return True
def remove(self):
"""Remove addon repository."""
"""Remove add-on repository."""
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()

View File

@ -1,4 +1,4 @@
"""Util addons functions."""
"""Util add-ons functions."""
import asyncio
import hashlib
import logging
@ -28,10 +28,6 @@ def rating_security(addon):
elif addon.apparmor == SECURITY_PROFILE:
rating += 1
# API Access
if addon.access_hassio_api or addon.access_homeassistant_api:
rating += -1
# Privileged options
if addon.privileged in (PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN,
PRIVILEGED_SYS_RAWIO, PRIVILEGED_SYS_PTRACE):
@ -78,7 +74,7 @@ def extract_hash_from_path(path):
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):
"""Return False if not installed or the function."""
if not addon.is_installed:

View File

@ -1,4 +1,4 @@
"""Validate addons options schema."""
"""Validate add-ons options schema."""
import logging
import re
import uuid
@ -20,18 +20,19 @@ from ..const import (
ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY,
ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API, ATTR_PROTECTED,
ATTR_FULL_ACCESS, ATTR_ACCESS_TOKEN, ATTR_HOST_PID, ATTR_HASSIO_ROLE,
ATTR_MACHINE,
PRIVILEGED_NET_ADMIN, PRIVILEGED_SYS_ADMIN, PRIVILEGED_SYS_RAWIO,
PRIVILEGED_IPC_LOCK, PRIVILEGED_SYS_TIME, PRIVILEGED_SYS_NICE,
PRIVILEGED_SYS_RESOURCE, PRIVILEGED_SYS_PTRACE,
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__)
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_DISCOVERY = re.compile(r"^(?P<component>\w*)(?:/(?P<platform>\w*>))?$")
RE_SERVICE = re.compile(r"^(?P<service>mqtt):(?P<rights>provide|want|need)$")
V_STR = 'str'
V_INT = 'int'
@ -57,6 +58,12 @@ ARCH_ALL = [
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_ONCE, STARTUP_INITIALIZE, STARTUP_SYSTEM, STARTUP_SERVICES,
STARTUP_APPLICATION
@ -105,6 +112,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
vol.Required(ATTR_DESCRIPTON): vol.Coerce(str),
vol.Optional(ATTR_URL): vol.Url(),
vol.Optional(ATTR_ARCH, default=ARCH_ALL): [vol.In(ARCH_ALL)],
vol.Optional(ATTR_MACHINE): [vol.In(MACHINE_ALL)],
vol.Required(ATTR_STARTUP):
vol.All(_simple_startup, vol.In(STARTUP_ALL)),
vol.Required(ATTR_BOOT):
@ -135,7 +143,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
vol.Optional(ATTR_LEGACY, default=False): vol.Boolean(),
vol.Optional(ATTR_DOCKER_API, default=False): vol.Boolean(),
vol.Optional(ATTR_SERVICES): [vol.Match(RE_SERVICE)],
vol.Optional(ATTR_DISCOVERY): [vol.Match(RE_DISCOVERY)],
vol.Optional(ATTR_DISCOVERY): [vol.In(DISCOVERY_SERVICES)],
vol.Required(ATTR_OPTIONS): dict,
vol.Required(ATTR_SCHEMA): vol.Any(vol.Schema({
vol.Coerce(str): vol.Any(SCHEMA_ELEMENT, [
@ -177,8 +185,7 @@ SCHEMA_BUILD_CONFIG = vol.Schema({
# pylint: disable=no-value-for-parameter
SCHEMA_ADDON_USER = vol.Schema({
vol.Required(ATTR_VERSION): vol.Coerce(str),
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
vol.Match(r"^[0-9a-f]{32}$"),
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex): UUID_MATCH,
vol.Optional(ATTR_ACCESS_TOKEN): vol.Match(r"^[0-9a-f]{64}$"),
vol.Optional(ATTR_OPTIONS, default=dict): dict,
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
@ -218,7 +225,7 @@ SCHEMA_ADDON_SNAPSHOT = vol.Schema({
def validate_options(raw_schema):
"""Validate schema."""
def validate(struct):
"""Create schema validator for addons options."""
"""Create schema validator for add-ons options."""
options = {}
# read options

View File

@ -1,4 +1,4 @@
"""Init file for HassIO rest api."""
"""Init file for Hass.io RESTful API."""
import logging
from pathlib import Path
@ -14,6 +14,7 @@ from .proxy import APIProxy
from .supervisor import APISupervisor
from .snapshots import APISnapshots
from .services import APIServices
from .version import APIVersion
from .security import SecurityMiddleware
from ..coresys import CoreSysAttributes
@ -21,10 +22,10 @@ _LOGGER = logging.getLogger(__name__)
class RestAPI(CoreSysAttributes):
"""Handle rest api for hassio."""
"""Handle RESTful API for Hass.io."""
def __init__(self, coresys):
"""Initialize docker base wrapper."""
"""Initialize Docker base wrapper."""
self.coresys = coresys
self.security = SecurityMiddleware(coresys)
self.webapp = web.Application(
@ -47,9 +48,10 @@ class RestAPI(CoreSysAttributes):
self._register_snapshots()
self._register_discovery()
self._register_services()
self._register_version()
def _register_host(self):
"""Register hostcontrol function."""
"""Register hostcontrol functions."""
api_host = APIHost()
api_host.coresys = self.coresys
@ -69,7 +71,7 @@ class RestAPI(CoreSysAttributes):
])
def _register_hassos(self):
"""Register hassos function."""
"""Register HassOS functions."""
api_hassos = APIHassOS()
api_hassos.coresys = self.coresys
@ -81,7 +83,7 @@ class RestAPI(CoreSysAttributes):
])
def _register_hardware(self):
"""Register hardware function."""
"""Register hardware functions."""
api_hardware = APIHardware()
api_hardware.coresys = self.coresys
@ -90,8 +92,17 @@ class RestAPI(CoreSysAttributes):
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):
"""Register supervisor function."""
"""Register Supervisor functions."""
api_supervisor = APISupervisor()
api_supervisor.coresys = self.coresys
@ -106,7 +117,7 @@ class RestAPI(CoreSysAttributes):
])
def _register_homeassistant(self):
"""Register homeassistant function."""
"""Register Home Assistant functions."""
api_hass = APIHomeAssistant()
api_hass.coresys = self.coresys
@ -137,7 +148,7 @@ class RestAPI(CoreSysAttributes):
])
def _register_addons(self):
"""Register homeassistant function."""
"""Register Add-on functions."""
api_addons = APIAddons()
api_addons.coresys = self.coresys
@ -163,7 +174,7 @@ class RestAPI(CoreSysAttributes):
])
def _register_snapshots(self):
"""Register snapshots function."""
"""Register snapshots functions."""
api_snapshots = APISnapshots()
api_snapshots.coresys = self.coresys
@ -183,6 +194,7 @@ class RestAPI(CoreSysAttributes):
])
def _register_services(self):
"""Register services functions."""
api_services = APIServices()
api_services.coresys = self.coresys
@ -194,19 +206,20 @@ class RestAPI(CoreSysAttributes):
])
def _register_discovery(self):
"""Register discovery functions."""
api_discovery = APIDiscovery()
api_discovery.coresys = self.coresys
self.webapp.add_routes([
web.get('/services/discovery', api_discovery.list),
web.get('/services/discovery/{uuid}', api_discovery.get_discovery),
web.delete('/services/discovery/{uuid}',
web.get('/discovery', api_discovery.list),
web.get('/discovery/{uuid}', api_discovery.get_discovery),
web.delete('/discovery/{uuid}',
api_discovery.del_discovery),
web.post('/services/discovery', api_discovery.set_discovery),
web.post('/discovery', api_discovery.set_discovery),
])
def _register_panel(self):
"""Register panel for homeassistant."""
"""Register panel for Home Assistant."""
panel_dir = Path(__file__).parent.joinpath("panel")
def create_response(panel_file):
@ -234,7 +247,7 @@ class RestAPI(CoreSysAttributes):
self.webapp.add_routes([web.static('/app', panel_dir)])
async def start(self):
"""Run rest api webserver."""
"""Run RESTful API webserver."""
await self._runner.setup()
self._site = web.TCPSite(
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)
async def stop(self):
"""Stop rest api webserver."""
"""Stop RESTful API webserver."""
if not self._site:
return

View File

@ -1,4 +1,4 @@
"""Init file for HassIO homeassistant rest api."""
"""Init file for Hass.io Home Assistant RESTful API."""
import asyncio
import logging
@ -20,11 +20,10 @@ from ..const import (
ATTR_NETWORK_RX, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_ICON, ATTR_SERVICES,
ATTR_DISCOVERY, ATTR_APPARMOR, ATTR_DEVICETREE, ATTR_DOCKER_API,
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)
from ..coresys import CoreSysAttributes
from ..validate import DOCKER_PORTS, ALSA_DEVICE
from ..exceptions import APINotSupportedError
_LOGGER = logging.getLogger(__name__)
@ -48,7 +47,7 @@ SCHEMA_SECURITY = vol.Schema({
class APIAddons(CoreSysAttributes):
"""Handle rest api for addons functions."""
"""Handle RESTful API for add-on functions."""
def _extract_addon(self, request, check_installed=True):
"""Return addon, throw an exception it it doesn't exist."""
@ -56,7 +55,7 @@ class APIAddons(CoreSysAttributes):
# Lookup itself
if addon_slug == 'self':
addon_slug = request.get(REQUEST_FROM)
return request.get(REQUEST_FROM)
addon = self.sys_addons.get(addon_slug)
if not addon:
@ -67,17 +66,9 @@ class APIAddons(CoreSysAttributes):
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
async def list(self, request):
"""Return all addons / repositories ."""
"""Return all add-ons or repositories."""
data_addons = []
for addon in self.sys_addons.list_addons:
data_addons.append({
@ -86,7 +77,7 @@ class APIAddons(CoreSysAttributes):
ATTR_DESCRIPTON: addon.description,
ATTR_VERSION: addon.last_version,
ATTR_INSTALLED: addon.version_installed,
ATTR_ARCH: addon.supported_arch,
ATTR_AVAILABLE: addon.available,
ATTR_DETACHED: addon.is_detached,
ATTR_REPOSITORY: addon.repository,
ATTR_BUILD: addon.need_build,
@ -112,13 +103,13 @@ class APIAddons(CoreSysAttributes):
@api_process
async def reload(self, request):
"""Reload all addons data."""
"""Reload all add-on data."""
await asyncio.shield(self.sys_addons.reload())
return True
@api_process
async def info(self, request):
"""Return addon information."""
"""Return add-on information."""
addon = self._extract_addon(request, check_installed=False)
return {
@ -135,8 +126,11 @@ class APIAddons(CoreSysAttributes):
ATTR_RATING: rating_security(addon),
ATTR_BOOT: addon.boot,
ATTR_OPTIONS: addon.options,
ATTR_ARCH: addon.supported_arch,
ATTR_MACHINE: addon.supported_machine,
ATTR_URL: addon.url,
ATTR_DETACHED: addon.is_detached,
ATTR_AVAILABLE: addon.available,
ATTR_BUILD: addon.need_build,
ATTR_NETWORK: addon.ports,
ATTR_HOST_NETWORK: addon.host_network,
@ -146,7 +140,7 @@ class APIAddons(CoreSysAttributes):
ATTR_PRIVILEGED: addon.privileged,
ATTR_FULL_ACCESS: addon.with_full_access,
ATTR_APPARMOR: addon.apparmor,
ATTR_DEVICES: self._pretty_devices(addon),
ATTR_DEVICES: _pretty_devices(addon),
ATTR_ICON: addon.with_icon,
ATTR_LOGO: addon.with_logo,
ATTR_CHANGELOG: addon.with_changelog,
@ -161,13 +155,13 @@ class APIAddons(CoreSysAttributes):
ATTR_AUDIO: addon.with_audio,
ATTR_AUDIO_INPUT: addon.audio_input,
ATTR_AUDIO_OUTPUT: addon.audio_output,
ATTR_SERVICES: addon.services,
ATTR_SERVICES: _pretty_services(addon),
ATTR_DISCOVERY: addon.discovery,
}
@api_process
async def options(self, request):
"""Store user options for addon."""
"""Store user options for add-on."""
addon = self._extract_addon(request)
addon_schema = SCHEMA_OPTIONS.extend({
@ -194,15 +188,8 @@ class APIAddons(CoreSysAttributes):
@api_process
async def security(self, request):
"""Store security options for addon."""
"""Store security options for add-on."""
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)
if ATTR_PROTECTED in body:
@ -233,19 +220,19 @@ class APIAddons(CoreSysAttributes):
@api_process
def install(self, request):
"""Install addon."""
"""Install add-on."""
addon = self._extract_addon(request, check_installed=False)
return asyncio.shield(addon.install())
@api_process
def uninstall(self, request):
"""Uninstall addon."""
"""Uninstall add-on."""
addon = self._extract_addon(request)
return asyncio.shield(addon.uninstall())
@api_process
def start(self, request):
"""Start addon."""
"""Start add-on."""
addon = self._extract_addon(request)
# check options
@ -259,13 +246,13 @@ class APIAddons(CoreSysAttributes):
@api_process
def stop(self, request):
"""Stop addon."""
"""Stop add-on."""
addon = self._extract_addon(request)
return asyncio.shield(addon.stop())
@api_process
def update(self, request):
"""Update addon."""
"""Update add-on."""
addon = self._extract_addon(request)
if addon.last_version == addon.version_installed:
@ -275,13 +262,13 @@ class APIAddons(CoreSysAttributes):
@api_process
def restart(self, request):
"""Restart addon."""
"""Restart add-on."""
addon = self._extract_addon(request)
return asyncio.shield(addon.restart())
@api_process
def rebuild(self, request):
"""Rebuild local build addon."""
"""Rebuild local build add-on."""
addon = self._extract_addon(request)
if not addon.need_build:
raise RuntimeError("Only local build addons are supported")
@ -290,13 +277,13 @@ class APIAddons(CoreSysAttributes):
@api_process_raw(CONTENT_TYPE_BINARY)
def logs(self, request):
"""Return logs from addon."""
"""Return logs from add-on."""
addon = self._extract_addon(request)
return addon.logs()
@api_process_raw(CONTENT_TYPE_PNG)
async def icon(self, request):
"""Return icon from addon."""
"""Return icon from add-on."""
addon = self._extract_addon(request, check_installed=False)
if not addon.with_icon:
raise RuntimeError("No icon found!")
@ -306,7 +293,7 @@ class APIAddons(CoreSysAttributes):
@api_process_raw(CONTENT_TYPE_PNG)
async def logo(self, request):
"""Return logo from addon."""
"""Return logo from add-on."""
addon = self._extract_addon(request, check_installed=False)
if not addon.with_logo:
raise RuntimeError("No logo found!")
@ -316,7 +303,7 @@ class APIAddons(CoreSysAttributes):
@api_process_raw(CONTENT_TYPE_TEXT)
async def changelog(self, request):
"""Return changelog from addon."""
"""Return changelog from add-on."""
addon = self._extract_addon(request, check_installed=False)
if not addon.with_changelog:
raise RuntimeError("No changelog found!")
@ -326,10 +313,26 @@ class APIAddons(CoreSysAttributes):
@api_process
async def stdin(self, request):
"""Write to stdin of addon."""
"""Write to stdin of add-on."""
addon = self._extract_addon(request)
if not addon.with_stdin:
raise RuntimeError("STDIN not supported by addon")
raise RuntimeError("STDIN not supported by add-on")
data = await request.read()
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

View File

@ -1,38 +1,48 @@
"""Init file for HassIO network rest api."""
"""Init file for Hass.io network RESTful API."""
import voluptuous as vol
from .utils import api_process, api_validate
from ..const import (
ATTR_PROVIDER, ATTR_UUID, ATTR_COMPONENT, ATTR_PLATFORM, ATTR_CONFIG,
ATTR_DISCOVERY, REQUEST_FROM)
ATTR_ADDON, ATTR_UUID, ATTR_COMPONENT, ATTR_PLATFORM, ATTR_CONFIG,
ATTR_DISCOVERY, ATTR_SERVICE, REQUEST_FROM)
from ..coresys import CoreSysAttributes
from ..exceptions import APIError, APIForbidden
from ..validate import SERVICE_ALL
SCHEMA_DISCOVERY = vol.Schema({
vol.Required(ATTR_SERVICE): SERVICE_ALL,
vol.Required(ATTR_COMPONENT): vol.Coerce(str),
vol.Optional(ATTR_PLATFORM): vol.Any(None, vol.Coerce(str)),
vol.Optional(ATTR_CONFIG): vol.Any(None, dict),
vol.Optional(ATTR_PLATFORM): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_CONFIG): vol.Maybe(dict),
})
class APIDiscovery(CoreSysAttributes):
"""Handle rest api for discovery functions."""
"""Handle RESTful API for discovery functions."""
def _extract_message(self, request):
"""Extract discovery message from URL."""
message = self.sys_discovery.get(request.match_info.get('uuid'))
if not message:
raise RuntimeError("Discovery message not found")
raise APIError("Discovery message not found")
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
async def list(self, request):
"""Show register services."""
self._check_permission_ha(request)
discovery = []
for message in self.sys_discovery.list_messages:
discovery.append({
ATTR_PROVIDER: message.provider,
ATTR_ADDON: message.addon,
ATTR_SERVICE: message.service,
ATTR_UUID: message.uuid,
ATTR_COMPONENT: message.component,
ATTR_PLATFORM: message.platform,
@ -45,8 +55,14 @@ class APIDiscovery(CoreSysAttributes):
async def set_discovery(self, request):
"""Write data into a discovery pipeline."""
body = await api_validate(SCHEMA_DISCOVERY, request)
message = self.sys_discovery.send(
provider=request[REQUEST_FROM], **body)
addon = request[REQUEST_FROM]
# 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}
@ -55,8 +71,12 @@ class APIDiscovery(CoreSysAttributes):
"""Read data into a discovery message."""
message = self._extract_message(request)
# HomeAssistant?
self._check_permission_ha(request)
return {
ATTR_PROVIDER: message.provider,
ATTR_ADDON: message.addon,
ATTR_SERVICE: message.service,
ATTR_UUID: message.uuid,
ATTR_COMPONENT: message.component,
ATTR_PLATFORM: message.platform,
@ -67,6 +87,11 @@ class APIDiscovery(CoreSysAttributes):
async def del_discovery(self, request):
"""Delete data into a discovery message."""
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)
return True

View File

@ -1,4 +1,4 @@
"""Init file for HassIO hardware rest api."""
"""Init file for Hass.io hardware RESTful API."""
import logging
from .utils import api_process
@ -10,7 +10,7 @@ _LOGGER = logging.getLogger(__name__)
class APIHardware(CoreSysAttributes):
"""Handle rest api for hardware functions."""
"""Handle RESTful API for hardware functions."""
@api_process
async def info(self, request):

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io hassos rest api."""
"""Init file for Hass.io HassOS RESTful API."""
import asyncio
import logging
@ -18,11 +18,11 @@ SCHEMA_VERSION = vol.Schema({
class APIHassOS(CoreSysAttributes):
"""Handle rest api for hassos functions."""
"""Handle RESTful API for HassOS functions."""
@api_process
async def info(self, request):
"""Return hassos information."""
"""Return HassOS information."""
return {
ATTR_VERSION: self.sys_hassos.version,
ATTR_VERSION_CLI: self.sys_hassos.version_cli,

View File

@ -1,4 +1,4 @@
"""Init file for HassIO homeassistant rest api."""
"""Init file for Hass.io Home Assistant RESTful API."""
import asyncio
import logging
@ -13,6 +13,7 @@ from ..const import (
ATTR_REFRESH_TOKEN, CONTENT_TYPE_BINARY)
from ..coresys import CoreSysAttributes
from ..validate import NETWORK_PORT, DOCKER_IMAGE
from ..exceptions import APIError
_LOGGER = logging.getLogger(__name__)
@ -39,7 +40,7 @@ SCHEMA_VERSION = vol.Schema({
class APIHomeAssistant(CoreSysAttributes):
"""Handle rest api for homeassistant functions."""
"""Handle RESTful API for Home Assistant functions."""
@api_process
async def info(self, request):
@ -59,7 +60,7 @@ class APIHomeAssistant(CoreSysAttributes):
@api_process
async def options(self, request):
"""Set homeassistant options."""
"""Set Home Assistant options."""
body = await api_validate(SCHEMA_OPTIONS, request)
if ATTR_IMAGE in body and ATTR_LAST_VERSION in body:
@ -94,7 +95,7 @@ class APIHomeAssistant(CoreSysAttributes):
"""Return resource information."""
stats = await self.sys_homeassistant.stats()
if not stats:
raise RuntimeError("No stats available")
raise APIError("No stats available")
return {
ATTR_CPU_PERCENT: stats.cpu_percent,
@ -108,7 +109,7 @@ class APIHomeAssistant(CoreSysAttributes):
@api_process
async def update(self, request):
"""Update homeassistant."""
"""Update Home Assistant."""
body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.sys_homeassistant.last_version)
@ -116,29 +117,29 @@ class APIHomeAssistant(CoreSysAttributes):
@api_process
def stop(self, request):
"""Stop homeassistant."""
"""Stop Home Assistant."""
return asyncio.shield(self.sys_homeassistant.stop())
@api_process
def start(self, request):
"""Start homeassistant."""
"""Start Home Assistant."""
return asyncio.shield(self.sys_homeassistant.start())
@api_process
def restart(self, request):
"""Restart homeassistant."""
"""Restart Home Assistant."""
return asyncio.shield(self.sys_homeassistant.restart())
@api_process_raw(CONTENT_TYPE_BINARY)
def logs(self, request):
"""Return homeassistant docker logs."""
"""Return Home Assistant Docker logs."""
return self.sys_homeassistant.logs()
@api_process
async def check(self, request):
"""Check config of homeassistant."""
"""Check configuration of Home Assistant."""
result = await self.sys_homeassistant.check_config()
if not result.valid:
raise RuntimeError(result.log)
raise APIError(result.log)
return True

View File

@ -1,4 +1,4 @@
"""Init file for HassIO host rest api."""
"""Init file for Hass.io host RESTful API."""
import asyncio
import logging
@ -21,7 +21,7 @@ SCHEMA_OPTIONS = vol.Schema({
class APIHost(CoreSysAttributes):
"""Handle rest api for host functions."""
"""Handle RESTful API for host functions."""
@api_process
async def info(self, request):

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.

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.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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
*/

Binary file not shown.

File diff suppressed because one or more lines are too long

View 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

Binary file not shown.

View 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

View File

@ -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
*/

File diff suppressed because one or more lines are too long

View File

@ -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
*/
/**
@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
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
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

Binary file not shown.

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.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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.

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@ import aiohttp
from aiohttp import web
from aiohttp.web_exceptions import (
HTTPBadGateway, HTTPInternalServerError, HTTPUnauthorized)
from aiohttp.hdrs import CONTENT_TYPE
from aiohttp.hdrs import CONTENT_TYPE, AUTHORIZATION
import async_timeout
from ..const import HEADER_HA_ACCESS
@ -18,17 +18,17 @@ _LOGGER = logging.getLogger(__name__)
class APIProxy(CoreSysAttributes):
"""API Proxy for Home-Assistant."""
"""API Proxy for Home Assistant."""
def _check_access(self, request):
"""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)
addon = self.sys_addons.from_token(hassio_token)
# REMOVE 132
if not addon:
addon = self.sys_addons.from_uuid(hassio_token)
if not addon:
_LOGGER.warning("Unknown Home Assistant API access!")
elif not addon.access_homeassistant_api:
@ -41,7 +41,7 @@ class APIProxy(CoreSysAttributes):
@asynccontextmanager
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:
# read data
with async_timeout.timeout(30):
@ -76,7 +76,7 @@ class APIProxy(CoreSysAttributes):
"""Proxy HomeAssistant EventStream Requests."""
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:
response = web.StreamResponse()
response.content_type = request.headers.get(CONTENT_TYPE)
@ -93,7 +93,7 @@ class APIProxy(CoreSysAttributes):
finally:
client.close()
_LOGGER.info("Home-Assistant EventStream close")
_LOGGER.info("Home Assistant EventStream close")
return response
@ -112,14 +112,14 @@ class APIProxy(CoreSysAttributes):
)
async def _websocket_client(self):
"""Initialize a websocket api connection."""
"""Initialize a WebSocket API connection."""
url = f"{self.sys_homeassistant.api_url}/api/websocket"
try:
client = await self.sys_websession_ssl.ws_connect(
url, heartbeat=60, verify_ssl=False)
# handle authentication
# Handle authentication
data = await client.receive_json()
if data.get('type') == 'auth_ok':
@ -128,7 +128,7 @@ class APIProxy(CoreSysAttributes):
if data.get('type') != 'auth_required':
# Invalid protocol
_LOGGER.error(
'Got unexpected response from HA websocket: %s', data)
"Got unexpected response from HA WebSocket: %s", data)
raise HTTPBadGateway()
if self.sys_homeassistant.refresh_token:
@ -157,15 +157,15 @@ class APIProxy(CoreSysAttributes):
raise HomeAssistantAuthError()
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:
_LOGGER.error("Failed authentication to HomeAssistant websocket")
_LOGGER.error("Failed authentication to Home Assistant WebSocket")
raise HTTPBadGateway()
async def websocket(self, request):
"""Initialize a websocket api connection."""
_LOGGER.info("Home-Assistant Websocket API request initialze")
"""Initialize a WebSocket API connection."""
_LOGGER.info("Home Assistant WebSocket API request initialize")
# init server
server = web.WebSocketResponse(heartbeat=60)
@ -184,19 +184,15 @@ class APIProxy(CoreSysAttributes):
response.get('access_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:
_LOGGER.warning("Unauthorized websocket access!")
_LOGGER.warning("Unauthorized WebSocket access!")
await server.send_json({
'type': 'auth_invalid',
'message': 'Invalid access',
})
return server
_LOGGER.info("Websocket access from %s", addon.slug)
_LOGGER.info("WebSocket access from %s", addon.slug)
await server.send_json({
'type': 'auth_ok',
@ -209,7 +205,7 @@ class APIProxy(CoreSysAttributes):
# init connection to hass
client = await self._websocket_client()
_LOGGER.info("Home-Assistant Websocket API request running")
_LOGGER.info("Home Assistant WebSocket API request running")
try:
client_read = None
server_read = None
@ -243,7 +239,7 @@ class APIProxy(CoreSysAttributes):
pass
except RuntimeError as err:
_LOGGER.info("Home-Assistant Websocket API error: %s", err)
_LOGGER.info("Home Assistant WebSocket API error: %s", err)
finally:
if client_read:
@ -255,5 +251,5 @@ class APIProxy(CoreSysAttributes):
await client.close()
await server.close()
_LOGGER.info("Home-Assistant Websocket API connection is closed")
_LOGGER.info("Home Assistant WebSocket API connection is closed")
return server

View File

@ -12,22 +12,30 @@ from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__)
# Block Anytime
BLACKLIST = re.compile(
r"^(?:"
r"|/homeassistant/api/hassio/.*"
r")$"
)
# Free to call or have own security concepts
NO_SECURITY_CHECK = re.compile(
r"^(?:"
r"|/homeassistant/api/.*"
r"|/homeassistant/websocket"
r"|/supervisor/ping"
r"|/services.*"
r")$"
)
# Can called by every add-on
ADDONS_API_BYPASS = re.compile(
r"^(?:"
r"|/homeassistant/info"
r"|/supervisor/info"
r"|/addons(?:/self/(?!security)[^/]+)?"
r"|/addons/self/(?!security)[^/]+"
r"|/version"
r"|/services.*"
r"|/discovery.*"
r")$"
)
@ -36,6 +44,7 @@ ADDONS_ROLE_ACCESS = {
ROLE_DEFAULT: re.compile(
r"^(?:"
r"|/[^/]+/info"
r"|/addons"
r")$"
),
ROLE_HOMEASSISTANT: re.compile(
@ -50,13 +59,12 @@ ADDONS_ROLE_ACCESS = {
r"|/hardware/.+"
r"|/hassos/.+"
r"|/supervisor/.+"
r"|/addons/.+/(?!security|options).+"
r"|/addons(?:/self/(?!security).+)"
r"|/addons/[^/]+/(?!security).+"
r"|/snapshots.*"
r")$"
),
ROLE_ADMIN: re.compile(
r".+"
r".*"
),
}
@ -74,6 +82,11 @@ class SecurityMiddleware(CoreSysAttributes):
request_from = None
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
if NO_SECURITY_CHECK.match(request.path):
_LOGGER.debug("Passthrough %s", request.path)
@ -88,38 +101,34 @@ class SecurityMiddleware(CoreSysAttributes):
# UUID check need removed with 131
if hassio_token in (self.sys_homeassistant.uuid,
self.sys_homeassistant.hassio_token):
_LOGGER.debug("%s access from Home-Assistant", request.path)
request_from = 'homeassistant'
_LOGGER.debug("%s access from Home Assistant", request.path)
request_from = self.sys_homeassistant
# Host
if hassio_token == self.sys_machine_id:
_LOGGER.debug("%s access from Host", request.path)
request_from = 'host'
request_from = self.sys_host
# Add-on
addon = None
if hassio_token and not request_from:
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
if addon and ADDONS_API_BYPASS.match(request.path):
_LOGGER.debug("Passthrough %s from %s", request.path, addon.slug)
request_from = addon.slug
request_from = addon
elif addon and addon.access_hassio_api:
# Check Role
if ADDONS_ROLE_ACCESS[addon.hassio_role].match(request.path):
_LOGGER.info("%s access from %s", request.path, addon.slug)
request_from = addon.slug
request_from = addon
else:
_LOGGER.warning("%s no role for %s", request.path, addon.slug)
request_from = addon.slug # REMOVE: 132
if request_from:
request[REQUEST_FROM] = request_from
return await handler(request)
_LOGGER.warning("Invalid token for access %s", request.path)
_LOGGER.error("Invalid token for access %s", request.path)
raise HTTPForbidden()

View File

@ -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 ..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 ..exceptions import APIError, APIForbidden
class APIServices(CoreSysAttributes):
"""Handle rest api for services functions."""
"""Handle RESTful API for services functions."""
def _extract_service(self, request):
"""Return service, throw an exception if it doesn't exist."""
service = self.sys_services.get(request.match_info.get('service'))
if not service:
raise RuntimeError("Service does not exist")
raise APIError("Service does not exist")
return service
@ -25,7 +27,7 @@ class APIServices(CoreSysAttributes):
services.append({
ATTR_SLUG: service.slug,
ATTR_AVAILABLE: service.enabled,
ATTR_PROVIDER: service.provider,
ATTR_PROVIDERS: service.providers,
})
return {ATTR_SERVICES: services}
@ -35,21 +37,39 @@ class APIServices(CoreSysAttributes):
"""Write data into a service."""
service = self._extract_service(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
async def get_service(self, request):
"""Read data into a service."""
service = self._extract_service(request)
return {
ATTR_AVAILABLE: service.enabled,
service.slug: service.get_service_data(),
}
# Access
_check_access(request, service.slug)
if not service.enabled:
raise APIError("Service not enabled")
return service.get_service_data()
@api_process
async def del_service(self, request):
"""Delete data into a service."""
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!")

View File

@ -1,4 +1,4 @@
"""Init file for HassIO snapshot rest api."""
"""Init file for Hass.io snapshot RESTful API."""
import asyncio
import logging
from pathlib import Path
@ -14,6 +14,7 @@ from ..const import (
ATTR_HOMEASSISTANT, ATTR_VERSION, ATTR_SIZE, ATTR_FOLDERS, ATTR_TYPE,
ATTR_SNAPSHOTS, ATTR_PASSWORD, ATTR_PROTECTED, CONTENT_TYPE_TAR)
from ..coresys import CoreSysAttributes
from ..exceptions import APIError
_LOGGER = logging.getLogger(__name__)
@ -46,13 +47,13 @@ SCHEMA_SNAPSHOT_PARTIAL = SCHEMA_SNAPSHOT_FULL.extend({
class APISnapshots(CoreSysAttributes):
"""Handle rest api for snapshot functions."""
"""Handle RESTful API for snapshot functions."""
def _extract_snapshot(self, request):
"""Return snapshot, throw an exception if it doesn't exist."""
snapshot = self.sys_snapshots.get(request.match_info.get('snapshot'))
if not snapshot:
raise RuntimeError("Snapshot does not exist")
raise APIError("Snapshot does not exist")
return snapshot
@api_process

View File

@ -1,4 +1,4 @@
"""Init file for HassIO supervisor rest api."""
"""Init file for Hass.io Supervisor RESTful API."""
import asyncio
import logging
@ -13,7 +13,9 @@ from ..const import (
ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX, ATTR_BLK_READ,
ATTR_BLK_WRITE, CONTENT_TYPE_BINARY, ATTR_ICON)
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__)
@ -30,11 +32,11 @@ SCHEMA_VERSION = vol.Schema({
class APISupervisor(CoreSysAttributes):
"""Handle rest api for supervisor functions."""
"""Handle RESTful API for Supervisor functions."""
@api_process
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
@api_process
@ -68,7 +70,7 @@ class APISupervisor(CoreSysAttributes):
@api_process
async def options(self, request):
"""Set supervisor options."""
"""Set Supervisor options."""
body = await api_validate(SCHEMA_OPTIONS, request)
if ATTR_CHANNEL in body:
@ -93,7 +95,7 @@ class APISupervisor(CoreSysAttributes):
"""Return resource information."""
stats = await self.sys_supervisor.stats()
if not stats:
raise RuntimeError("No stats available")
raise APIError("No stats available")
return {
ATTR_CPU_PERCENT: stats.cpu_percent,
@ -107,19 +109,19 @@ class APISupervisor(CoreSysAttributes):
@api_process
async def update(self, request):
"""Update supervisor OS."""
"""Update Supervisor OS."""
body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.sys_updater.version_hassio)
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(
self.sys_supervisor.update(version))
@api_process
async def reload(self, request):
"""Reload addons, config etc."""
"""Reload add-ons, configuration, etc."""
tasks = [
self.sys_updater.reload(),
]
@ -128,11 +130,11 @@ class APISupervisor(CoreSysAttributes):
for result in results:
if result.exception() is not None:
raise RuntimeError("Some reload task fails!")
raise APIError("Some reload task fails!")
return True
@api_process_raw(CONTENT_TYPE_BINARY)
def logs(self, request):
"""Return supervisor docker logs."""
"""Return supervisor Docker logs."""
return self.sys_supervisor.logs()

View File

@ -1,4 +1,4 @@
"""Init file for HassIO util for rest api."""
"""Init file for Hass.io util for RESTful API."""
import json
import logging
@ -9,7 +9,7 @@ from voluptuous.humanize import humanize_error
from ..const import (
JSON_RESULT, JSON_DATA, JSON_MESSAGE, RESULT_OK, RESULT_ERROR,
CONTENT_TYPE_BINARY)
from ..exceptions import HassioError
from ..exceptions import HassioError, APIError, APIForbidden
_LOGGER = logging.getLogger(__name__)
@ -21,19 +21,19 @@ def json_loads(data):
try:
return json.loads(data)
except json.JSONDecodeError:
raise RuntimeError("Invalid json")
raise APIError("Invalid json")
def api_process(method):
"""Wrap function with true/false calls to rest api."""
async def wrap_api(api, *args, **kwargs):
"""Return api information."""
"""Return API information."""
try:
answer = await method(api, *args, **kwargs)
except HassioError:
return api_return_error()
except RuntimeError as err:
except (APIError, APIForbidden) as err:
return api_return_error(message=str(err))
except HassioError:
return api_return_error(message="Unknown Error, see logs")
if isinstance(answer, dict):
return api_return_ok(data=answer)
@ -55,7 +55,7 @@ def api_process_raw(content):
try:
msg_data = await method(api, *args, **kwargs)
msg_type = content
except RuntimeError as err:
except (APIError, APIForbidden) as err:
msg_data = str(err).encode()
msg_type = CONTENT_TYPE_BINARY
except HassioError:
@ -90,6 +90,6 @@ async def api_validate(schema, request):
try:
data = schema(data)
except vol.Invalid as ex:
raise RuntimeError(humanize_error(data, ex)) from None
raise APIError(humanize_error(data, ex)) from None
return data

26
hassio/api/version.py Normal file
View 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,
}

View File

@ -18,7 +18,7 @@ from .snapshots import SnapshotManager
from .tasks import Tasks
from .updater import Updater
from .services import ServiceManager
from .services import Discovery
from .discovery import Discovery
from .host import HostManager
from .dbus import DBusManager
from .hassos import HassOS

View File

@ -2,7 +2,7 @@
from pathlib import Path
from ipaddress import ip_network
HASSIO_VERSION = '131'
HASSIO_VERSION = '132'
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
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_UPDATER = Path(HASSIO_DATA, "updater.json")
FILE_HASSIO_SERVICES = Path(HASSIO_DATA, "services.json")
FILE_HASSIO_DISCOVERY = Path(HASSIO_DATA, "discovery.json")
SOCKET_DOCKER = Path("/var/run/docker.sock")
@ -74,6 +75,7 @@ ATTR_TYPE = 'type'
ATTR_SOURCE = 'source'
ATTR_FEATURES = 'features'
ATTR_ADDONS = 'addons'
ATTR_PROVIDERS = 'providers'
ATTR_VERSION = 'version'
ATTR_VERSION_LATEST = 'version_latest'
ATTR_AUTO_UART = 'auto_uart'
@ -107,8 +109,6 @@ ATTR_MAINTAINER = 'maintainer'
ATTR_PASSWORD = 'password'
ATTR_TOTP = 'totp'
ATTR_INITIALIZE = 'initialize'
ATTR_SESSION = 'session'
ATTR_SESSIONS = 'sessions'
ATTR_LOCATON = 'location'
ATTR_BUILD = 'build'
ATTR_DEVICES = 'devices'
@ -154,7 +154,7 @@ ATTR_MEMORY_LIMIT = 'memory_limit'
ATTR_MEMORY_USAGE = 'memory_usage'
ATTR_BLK_READ = 'blk_read'
ATTR_BLK_WRITE = 'blk_write'
ATTR_PROVIDER = 'provider'
ATTR_ADDON = 'addon'
ATTR_AVAILABLE = 'available'
ATTR_HOST = 'host'
ATTR_USERNAME = 'username'
@ -163,8 +163,8 @@ ATTR_DISCOVERY = 'discovery'
ATTR_PLATFORM = 'platform'
ATTR_COMPONENT = 'component'
ATTR_CONFIG = 'config'
ATTR_DISCOVERY_ID = 'discovery_id'
ATTR_SERVICES = 'services'
ATTR_SERVICE = 'service'
ATTR_DISCOVERY = 'discovery'
ATTR_PROTECTED = 'protected'
ATTR_CRYPTO = 'crypto'
@ -185,8 +185,12 @@ ATTR_FULL_ACCESS = 'full_access'
ATTR_PROTECTED = 'protected'
ATTR_RATING = 'rating'
ATTR_HASSIO_ROLE = 'hassio_role'
ATTR_SUPERVISOR = 'supervisor'
SERVICE_MQTT = 'mqtt'
PROVIDE_SERVICE = 'provide'
NEED_SERVICE = 'need'
WANT_SERVICE = 'want'
STARTUP_INITIALIZE = 'initialize'
STARTUP_SYSTEM = 'system'

View File

@ -52,6 +52,9 @@ class HassIO(CoreSysAttributes):
# load services
await self.sys_services.load()
# Load discovery
await self.sys_discovery.load()
# start dns forwarding
self.sys_create_task(self.sys_dns.start())

View File

@ -16,7 +16,7 @@ class Systemd(DBusInterface):
"""Systemd function handler."""
async def connect(self):
"""Connect do bus."""
"""Connect to D-Bus."""
try:
self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT)
except DBusError:

123
hassio/discovery.py Normal file
View 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)

View File

@ -1,4 +1,4 @@
"""Init file for HassIO docker object."""
"""Init file for Hass.io Docker object."""
from contextlib import suppress
import logging
@ -15,13 +15,13 @@ CommandReturn = attr.make_class('CommandReturn', ['exit_code', 'output'])
class DockerAPI:
"""Docker hassio wrapper.
"""Docker Hass.io wrapper.
This class is not AsyncIO safe!
"""
def __init__(self):
"""Initialize docker base wrapper."""
"""Initialize Docker base wrapper."""
self.docker = docker.DockerClient(
base_url="unix:/{}".format(str(SOCKET_DOCKER)),
version='auto', timeout=900)
@ -29,21 +29,21 @@ class DockerAPI:
@property
def images(self):
"""Return api images."""
"""Return API images."""
return self.docker.images
@property
def containers(self):
"""Return api containers."""
"""Return API containers."""
return self.docker.containers
@property
def api(self):
"""Return api containers."""
"""Return API containers."""
return self.docker.api
def run(self, image, **kwargs):
""""Create a docker and run it.
""""Create a Docker container and run it.
Need run inside executor.
"""
@ -51,7 +51,7 @@ class DockerAPI:
network_mode = kwargs.get('network_mode')
hostname = kwargs.get('hostname')
# setup network
# Setup network
kwargs['dns_search'] = ["."]
if network_mode:
kwargs['dns'] = [str(self.network.supervisor)]
@ -59,7 +59,7 @@ class DockerAPI:
else:
kwargs['network'] = None
# create container
# Create container
try:
container = self.docker.containers.create(image, **kwargs)
except docker.errors.DockerException as err:

View File

@ -1,4 +1,4 @@
"""Init file for HassIO addon docker object."""
"""Init file for Hass.io add-on Docker object."""
import logging
import os
from pathlib import Path
@ -19,45 +19,45 @@ AUDIO_DEVICE = "/dev/snd:/dev/snd:rwm"
class DockerAddon(DockerInterface):
"""Docker hassio wrapper for HomeAssistant."""
"""Docker Hass.io wrapper for Home Assistant."""
def __init__(self, coresys, slug):
"""Initialize docker homeassistant wrapper."""
"""Initialize Docker Home Assistant wrapper."""
super().__init__(coresys)
self._id = slug
@property
def addon(self):
"""Return addon of docker image."""
"""Return add-on of Docker image."""
return self.sys_addons.get(self._id)
@property
def image(self):
"""Return name of docker image."""
"""Return name of Docker image."""
return self.addon.image
@property
def timeout(self):
"""Return timeout for docker actions."""
"""Return timeout for Docker actions."""
return self.addon.timeout
@property
def version(self):
"""Return version of docker image."""
"""Return version of Docker image."""
if not self.addon.legacy:
return super().version
return self.addon.version_installed
@property
def arch(self):
"""Return arch of docker image."""
"""Return arch of Docker image."""
if not self.addon.legacy:
return super().arch
return self.sys_arch
@property
def name(self):
"""Return name of docker container."""
"""Return name of Docker container."""
return "addon_{}".format(self.addon.slug)
@property
@ -74,12 +74,12 @@ class DockerAddon(DockerInterface):
@property
def hostname(self):
"""Return slug/id of addon."""
"""Return slug/id of add-on."""
return self.addon.slug.replace('_', '-')
@property
def environment(self):
"""Return environment for docker add-on."""
"""Return environment for Docker add-on."""
addon_env = self.addon.environment or {}
# Need audio settings
@ -114,7 +114,7 @@ class DockerAddon(DockerInterface):
@property
def ports(self):
"""Filter None from addon ports."""
"""Filter None from add-on ports."""
if not self.addon.ports:
return None
@ -126,7 +126,7 @@ class DockerAddon(DockerInterface):
@property
def security_opt(self):
"""Controlling security opt."""
"""Controlling security options."""
security = []
# AppArmor
@ -144,7 +144,7 @@ class DockerAddon(DockerInterface):
@property
def tmpfs(self):
"""Return tmpfs for docker add-on."""
"""Return tmpfs for Docker add-on."""
options = self.addon.tmpfs
if options:
return {"/tmpfs": f"{options}"}
@ -160,14 +160,14 @@ class DockerAddon(DockerInterface):
@property
def network_mode(self):
"""Return network mode for addon."""
"""Return network mode for add-on."""
if self.addon.host_network:
return 'host'
return None
@property
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:
return 'host'
return None
@ -242,7 +242,7 @@ class DockerAddon(DockerInterface):
},
})
# Host dbus system
# Host D-Bus system
if self.addon.host_dbus:
volumes.update({
"/var/run/dbus": {
@ -259,7 +259,7 @@ class DockerAddon(DockerInterface):
return volumes
def _run(self):
"""Run docker image.
"""Run Docker image.
Need run inside executor.
"""
@ -269,7 +269,7 @@ class DockerAddon(DockerInterface):
# Security check
if not self.addon.protected:
_LOGGER.warning(
"%s run with disabled proteced mode!", self.addon.name)
"%s run with disabled protected mode!", self.addon.name)
# cleanup
self._stop()
@ -296,13 +296,13 @@ class DockerAddon(DockerInterface):
)
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)
return ret
def _install(self, tag):
"""Pull docker image or build it.
"""Pull Docker image or build it.
Need run inside executor.
"""
@ -312,7 +312,7 @@ class DockerAddon(DockerInterface):
return super()._install(tag)
def _build(self, tag):
"""Build a docker container.
"""Build a Docker container.
Need run inside executor.
"""
@ -329,7 +329,7 @@ class DockerAddon(DockerInterface):
# Update meta data
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)
return False
@ -403,7 +403,7 @@ class DockerAddon(DockerInterface):
return False
try:
# load needed docker objects
# Load needed docker objects
container = self.sys_docker.containers.get(self.name)
socket = container.attach_socket(params={'stdin': 1, 'stream': 1})
except docker.errors.DockerException as err:
@ -411,7 +411,7 @@ class DockerAddon(DockerInterface):
return False
try:
# write to stdin
# Write to stdin
data += b"\n"
os.write(socket.fileno(), data)
socket.close()

View File

@ -10,11 +10,11 @@ _LOGGER = logging.getLogger(__name__)
class DockerHassOSCli(DockerInterface, CoreSysAttributes):
"""Docker hassio wrapper for HassOS Cli."""
"""Docker Hass.io wrapper for HassOS Cli."""
@property
def image(self):
"""Return name of HassOS cli image."""
"""Return name of HassOS CLI image."""
return f"homeassistant/{self.sys_arch}-hassio-cli"
def _stop(self):
@ -22,16 +22,16 @@ class DockerHassOSCli(DockerInterface, CoreSysAttributes):
return True
def _attach(self):
"""Attach to running docker container.
"""Attach to running Docker container.
Need run inside executor.
"""
try:
image = self.sys_docker.images.get(self.image)
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:
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)

View File

@ -1,4 +1,4 @@
"""Init file for HassIO docker object."""
"""Init file for Hass.io Docker object."""
import logging
import docker
@ -12,35 +12,35 @@ HASS_DOCKER_NAME = 'homeassistant'
class DockerHomeAssistant(DockerInterface):
"""Docker hassio wrapper for HomeAssistant."""
"""Docker Hass.io wrapper for Home Assistant."""
@property
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']:
return self._meta['Config']['Labels'][LABEL_MACHINE]
return None
@property
def image(self):
"""Return name of docker image."""
"""Return name of Docker image."""
return self.sys_homeassistant.image
@property
def name(self):
"""Return name of docker container."""
"""Return name of Docker container."""
return HASS_DOCKER_NAME
@property
def devices(self):
"""Create list of special device to map into docker."""
"""Create list of special device to map into Docker."""
devices = []
for device in self.sys_hardware.serial_devices:
devices.append(f"{device}:{device}:rwm")
return devices or None
def _run(self):
"""Run docker image.
"""Run Docker image.
Need run inside executor.
"""
@ -108,7 +108,7 @@ class DockerHomeAssistant(DockerInterface):
)
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)
def _is_initialize(self):

View File

@ -1,4 +1,4 @@
"""Interface class for HassIO docker object."""
"""Interface class for Hass.io Docker object."""
import asyncio
from contextlib import suppress
import logging
@ -14,27 +14,27 @@ _LOGGER = logging.getLogger(__name__)
class DockerInterface(CoreSysAttributes):
"""Docker hassio interface."""
"""Docker Hass.io interface."""
def __init__(self, coresys):
"""Initialize docker base wrapper."""
"""Initialize Docker base wrapper."""
self.coresys = coresys
self._meta = None
self.lock = asyncio.Lock(loop=coresys.loop)
@property
def timeout(self):
"""Return timeout for docker actions."""
"""Return timeout for Docker actions."""
return 30
@property
def name(self):
"""Return name of docker container."""
"""Return name of Docker container."""
return None
@property
def meta_config(self):
"""Return meta data of config for container/image."""
"""Return meta data of configuration for container/image."""
if not self._meta:
return {}
return self._meta.get('Config', {})
@ -42,21 +42,21 @@ class DockerInterface(CoreSysAttributes):
@property
def meta_labels(self):
"""Return meta data of labels for container/image."""
return self.meta_config.get('Labels', {})
return self.meta_config.get('Labels') or {}
@property
def image(self):
"""Return name of docker image."""
"""Return name of Docker image."""
return self.meta_config.get('Image')
@property
def version(self):
"""Return version of docker image."""
"""Return version of Docker image."""
return self.meta_labels.get(LABEL_VERSION)
@property
def arch(self):
"""Return arch of docker image."""
"""Return arch of Docker image."""
return self.meta_labels.get(LABEL_ARCH)
@property
@ -70,7 +70,7 @@ class DockerInterface(CoreSysAttributes):
return self.sys_run_in_executor(self._install, tag)
def _install(self, tag):
"""Pull docker image.
"""Pull Docker image.
Need run inside executor.
"""
@ -88,11 +88,11 @@ class DockerInterface(CoreSysAttributes):
return True
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)
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.
"""
@ -105,14 +105,14 @@ class DockerInterface(CoreSysAttributes):
return True
def is_running(self):
"""Return True if docker is Running.
"""Return True if Docker is running.
Return a Future.
"""
return self.sys_run_in_executor(self._is_running)
def _is_running(self):
"""Return True if docker is Running.
"""Return True if Docker is running.
Need run inside executor.
"""
@ -134,7 +134,7 @@ class DockerInterface(CoreSysAttributes):
@process_lock
def attach(self):
"""Attach to running docker container."""
"""Attach to running Docker container."""
return self.sys_run_in_executor(self._attach)
def _attach(self):
@ -157,11 +157,11 @@ class DockerInterface(CoreSysAttributes):
@process_lock
def run(self):
"""Run docker image."""
"""Run Docker image."""
return self.sys_run_in_executor(self._run)
def _run(self):
"""Run docker image.
"""Run Docker image.
Need run inside executor.
"""
@ -169,7 +169,7 @@ class DockerInterface(CoreSysAttributes):
@process_lock
def stop(self):
"""Stop/remove docker container."""
"""Stop/remove Docker container."""
return self.sys_run_in_executor(self._stop)
def _stop(self):
@ -183,19 +183,19 @@ class DockerInterface(CoreSysAttributes):
return False
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):
container.stop(timeout=self.timeout)
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)
return True
@process_lock
def remove(self):
"""Remove docker images."""
"""Remove Docker images."""
return self.sys_run_in_executor(self._remove)
def _remove(self):
@ -203,11 +203,11 @@ class DockerInterface(CoreSysAttributes):
Need run inside executor.
"""
# cleanup container
# Cleanup container
self._stop()
_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:
with suppress(docker.errors.ImageNotFound):
@ -227,7 +227,7 @@ class DockerInterface(CoreSysAttributes):
@process_lock
def update(self, tag):
"""Update a docker image."""
"""Update a Docker image."""
return self.sys_run_in_executor(self._update, tag)
def _update(self, tag):
@ -236,27 +236,27 @@ class DockerInterface(CoreSysAttributes):
Need run inside executor.
"""
_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):
return False
# stop container & cleanup
# Stop container & cleanup
self._stop()
self._cleanup()
return True
def logs(self):
"""Return docker logs of container.
"""Return Docker logs of container.
Return a Future.
"""
return self.sys_run_in_executor(self._logs)
def _logs(self):
"""Return docker logs of container.
"""Return Docker logs of container.
Need run inside executor.
"""
@ -268,7 +268,7 @@ class DockerInterface(CoreSysAttributes):
try:
return container.logs(tail=100, stdout=True, stderr=True)
except docker.errors.DockerException as err:
_LOGGER.warning("Can't grap logs from %s: %s", self.image, err)
_LOGGER.warning("Can't grep logs from %s: %s", self.image, err)
@process_lock
def cleanup(self):
@ -291,7 +291,7 @@ class DockerInterface(CoreSysAttributes):
continue
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)
return True

View File

@ -1,4 +1,4 @@
"""Internal network manager for HassIO."""
"""Internal network manager for Hass.io."""
import logging
import docker
@ -9,13 +9,13 @@ _LOGGER = logging.getLogger(__name__)
class DockerNetwork:
"""Internal HassIO Network.
"""Internal Hass.io Network.
This class is not AsyncIO safe!
"""
def __init__(self, dock):
"""Initialize internal hassio network."""
"""Initialize internal Hass.io network."""
self.docker = dock
self.network = self._get_network()
@ -44,7 +44,7 @@ class DockerNetwork:
try:
return self.docker.networks.get(DOCKER_NETWORK)
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(
subnet=str(DOCKER_NETWORK_MASK),
@ -61,7 +61,7 @@ class DockerNetwork:
})
def attach_container(self, container, alias=None, ipv4=None):
"""Attach container to hassio network.
"""Attach container to Hass.io network.
Need run inside executor.
"""
@ -77,7 +77,7 @@ class DockerNetwork:
return True
def detach_default_bridge(self, container):
"""Detach default docker bridge.
"""Detach default Docker bridge.
Need run inside executor.
"""

View File

@ -1,4 +1,4 @@
"""Calc & represent docker stats data."""
"""Calc and represent docker stats data."""
from contextlib import suppress
@ -6,7 +6,7 @@ class DockerStats:
"""Hold stats data from container inside."""
def __init__(self, stats):
"""Initialize docker stats."""
"""Initialize Docker stats."""
self._cpu = 0.0
self._network_rx = 0
self._network_tx = 0

View File

@ -1,4 +1,4 @@
"""Init file for HassIO docker object."""
"""Init file for Hass.io Docker object."""
import logging
import os
@ -11,11 +11,11 @@ _LOGGER = logging.getLogger(__name__)
class DockerSupervisor(DockerInterface, CoreSysAttributes):
"""Docker hassio wrapper for Supervisor."""
"""Docker Hass.io wrapper for Supervisor."""
@property
def name(self):
"""Return name of docker container."""
"""Return name of Docker container."""
return os.environ['SUPERVISOR_NAME']
def _attach(self):
@ -29,14 +29,14 @@ class DockerSupervisor(DockerInterface, CoreSysAttributes):
return False
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)
# if already attach
# If already attach
if container in self.sys_docker.network.containers:
return True
# attach to network
# Attach to network
return self.sys_docker.network.attach_container(
container, alias=['hassio'],
ipv4=self.sys_docker.network.supervisor)

View File

@ -81,13 +81,25 @@ class HostAppArmorError(HostError):
# API
class APIError(HassioError):
class APIError(HassioError, RuntimeError):
"""API errors."""
pass
class APINotSupportedError(HassioNotSupportedError):
"""API not supported error."""
class APIForbidden(APIError):
"""API forbidden error."""
pass
# Service / Discovery
class DiscoveryError(HassioError):
"""Discovery Errors."""
pass
class ServicesError(HassioError):
"""Services Errors."""
pass

View File

@ -439,19 +439,6 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
_LOGGER.warning("Home Assistant API config mismatch: %d", err)
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):
"""Block until Home-Assistant is booting up or startup timeout."""
start_time = time.monotonic()

View File

@ -1,4 +1,4 @@
"""Host function like audio/dbus/systemd."""
"""Host function like audio, D-Bus or systemd."""
from contextlib import suppress
import logging
@ -35,7 +35,7 @@ class HostManager(CoreSysAttributes):
@property
def apparmor(self):
"""Return host apparmor handler."""
"""Return host AppArmor handler."""
return self._apparmor
@property

View File

@ -1,4 +1,4 @@
"""Host Audio-support."""
"""Host Audio support."""
import logging
import json
from pathlib import Path
@ -19,7 +19,7 @@ class AlsaAudio(CoreSysAttributes):
"""Handle Audio ALSA host data."""
def __init__(self, coresys):
"""Initialize Alsa audio system."""
"""Initialize ALSA audio system."""
self.coresys = coresys
self._data = {
ATTR_INPUT: {},

View File

@ -13,7 +13,7 @@ SYSTEMD_SERVICES = {'hassos-apparmor.service', 'hassio-apparmor.service'}
class AppArmorControl(CoreSysAttributes):
"""Handle host apparmor controls."""
"""Handle host AppArmor controls."""
def __init__(self, coresys):
"""Initialize host power handling."""
@ -23,7 +23,7 @@ class AppArmorControl(CoreSysAttributes):
@property
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
def exists(self, profile):
@ -62,12 +62,12 @@ class AppArmorControl(CoreSysAttributes):
if self.available:
await self._reload_service()
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):
"""Load/Update a new/exists profile into AppArmor."""
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()
# Copy to AppArmor folder

View File

@ -24,7 +24,7 @@ class SystemControl(CoreSysAttributes):
if flag == HOSTNAME and self.sys_dbus.hostname.is_connected:
return
_LOGGER.error("No %s dbus connection available", flag)
_LOGGER.error("No %s D-Bus connection available", flag)
raise HostNotSupportedError()
async def reboot(self):
@ -51,6 +51,6 @@ class SystemControl(CoreSysAttributes):
"""Set local a new 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_host.info.update()

View File

@ -48,7 +48,7 @@ class InfoCenter(CoreSysAttributes):
async def update(self):
"""Update properties over dbus."""
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()
_LOGGER.info("Update local host information")

View File

@ -95,5 +95,5 @@ class ServiceInfo:
@staticmethod
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])

View File

@ -24,7 +24,7 @@ RE_TTY = re.compile(r"tty[A-Z]+")
class Hardware:
"""Represent an interface to procfs, sysfs and udev."""
"""Representation of an interface to procfs, sysfs and udev."""
def __init__(self):
"""Init hardware object."""

View File

@ -1,4 +1,4 @@
"""Schedule for HassIO."""
"""Schedule for Hass.io."""
import logging
from datetime import date, datetime, time, timedelta
@ -11,7 +11,7 @@ TASK = 'task'
class Scheduler:
"""Schedule task inside HassIO."""
"""Schedule task inside Hass.io."""
def __init__(self, loop):
"""Initialize task schedule."""
@ -22,18 +22,18 @@ class Scheduler:
def register_task(self, coro_callback, interval, repeat=True):
"""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)
# generate data
# Generate data
opts = {
CALL: coro_callback,
INTERVAL: interval,
REPEAT: repeat,
}
# schedule task
# Schedule task
self._data[task_id] = opts
self._schedule_task(interval, task_id)
@ -60,7 +60,7 @@ class Scheduler:
tomorrow = datetime.combine(
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():
calc = today
else:
@ -68,7 +68,7 @@ class Scheduler:
job = self.loop.call_at(calc.timestamp(), self._run_task, task_id)
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)
# Store job

View File

@ -1,6 +1,4 @@
"""Handle internal services discovery."""
from .discovery import Discovery # noqa
from .mqtt import MQTTService
from .data import ServicesData
from ..const import SERVICE_MQTT
@ -35,10 +33,6 @@ class ServiceManager(CoreSysAttributes):
for slug, service in AVAILABLE_SERVICES.items():
self.services_obj[slug] = service(self.coresys)
# Read exists discovery messages
self.sys_discovery.load()
def reset(self):
"""Reset available data."""
self.data.reset_data()
self.sys_discovery.load()

View File

@ -1,7 +1,7 @@
"""Handle service data for persistent supervisor reboot."""
from .validate import SCHEMA_SERVICES_FILE
from ..const import FILE_HASSIO_SERVICES, ATTR_DISCOVERY, SERVICE_MQTT
from .validate import SCHEMA_SERVICES_CONFIG
from ..const import FILE_HASSIO_SERVICES, SERVICE_MQTT
from ..utils.json import JsonConfig
@ -10,14 +10,9 @@ class ServicesData(JsonConfig):
def __init__(self):
"""Initialize services data."""
super().__init__(FILE_HASSIO_SERVICES, SCHEMA_SERVICES_FILE)
@property
def discovery(self):
"""Return discovery data for home-assistant."""
return self._data[ATTR_DISCOVERY]
super().__init__(FILE_HASSIO_SERVICES, SCHEMA_SERVICES_CONFIG)
@property
def mqtt(self):
"""Return settings for mqtt service."""
"""Return settings for MQTT service."""
return self._data[SERVICE_MQTT]

View File

@ -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

View File

@ -1,6 +1,7 @@
"""Interface for single service."""
from ..coresys import CoreSysAttributes
from ..const import PROVIDE_SERVICE
class ServiceInterface(CoreSysAttributes):
@ -26,9 +27,13 @@ class ServiceInterface(CoreSysAttributes):
return None
@property
def provider(self):
"""Return name of service provider."""
return None
def providers(self):
"""Return name of service providers addon."""
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
def enabled(self):
@ -45,10 +50,10 @@ class ServiceInterface(CoreSysAttributes):
return self._data
return None
def set_service_data(self, provider, data):
def set_service_data(self, addon, data):
"""Write the data into service object."""
raise NotImplementedError()
def del_service_data(self, provider):
def del_service_data(self, addon):
"""Remove the data from service object."""
raise NotImplementedError()

View File

@ -1,17 +1,16 @@
"""Provide MQTT Service."""
"""Provide the MQTT Service."""
import logging
from .interface import ServiceInterface
from .validate import SCHEMA_SERVICE_MQTT
from ..const import (
ATTR_PROVIDER, SERVICE_MQTT, ATTR_HOST, ATTR_PORT, ATTR_USERNAME,
ATTR_PASSWORD, ATTR_PROTOCOL, ATTR_DISCOVERY_ID)
from ..const import ATTR_ADDON, SERVICE_MQTT
from ..exceptions import ServicesError
_LOGGER = logging.getLogger(__name__)
class MQTTService(ServiceInterface):
"""Provide mqtt services."""
"""Provide MQTT services."""
@property
def slug(self):
@ -28,62 +27,24 @@ class MQTTService(ServiceInterface):
"""Return data schema of this service."""
return SCHEMA_SERVICE_MQTT
@property
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):
def set_service_data(self, addon, data):
"""Write the data into service object."""
if self.enabled:
_LOGGER.error("It is already a mqtt in use from %s", self.provider)
return False
_LOGGER.error(
"It is already a MQTT in use from %s", self._data[ATTR_ADDON])
raise ServicesError()
self._data.update(data)
self._data[ATTR_PROVIDER] = provider
self._data[ATTR_ADDON] = addon.slug
if provider == 'homeassistant':
_LOGGER.info("Use mqtt settings from Home-Assistant")
_LOGGER.info("Set %s as service provider for mqtt", addon.slug)
self.save()
return True
# discover mqtt to homeassistant
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):
def del_service_data(self, addon):
"""Remove the data from service object."""
if not self.enabled:
_LOGGER.warning("Can't remove not exists services.")
return False
discovery_id = self._data.get(ATTR_DISCOVERY_ID)
if discovery_id:
self.sys_discovery.remove(
self.sys_discovery.get(discovery_id))
_LOGGER.warning("Can't remove not exists services")
raise ServicesError()
self._data.clear()
self.save()
return True

View File

@ -1,23 +1,11 @@
"""Validate services schema."""
import voluptuous as vol
from ..const import (
SERVICE_MQTT, ATTR_HOST, ATTR_PORT, ATTR_PASSWORD, ATTR_USERNAME, ATTR_SSL,
ATTR_PROVIDER, ATTR_PROTOCOL, ATTR_DISCOVERY, ATTR_COMPONENT, ATTR_UUID,
ATTR_PLATFORM, ATTR_CONFIG, ATTR_DISCOVERY_ID)
ATTR_ADDON, ATTR_PROTOCOL)
from ..validate import NETWORK_PORT
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)
])
from ..utils.validate import schema_or
# pylint: disable=no-value-for-parameter
@ -33,12 +21,15 @@ SCHEMA_SERVICE_MQTT = vol.Schema({
SCHEMA_CONFIG_MQTT = SCHEMA_SERVICE_MQTT.extend({
vol.Required(ATTR_PROVIDER): vol.Coerce(str),
vol.Optional(ATTR_DISCOVERY_ID): vol.Match(r"^[0-9a-f]{32}$"),
vol.Required(ATTR_ADDON): vol.Coerce(str),
})
SCHEMA_SERVICES_FILE = vol.Schema({
vol.Optional(SERVICE_MQTT, default=dict): vol.Any({}, SCHEMA_CONFIG_MQTT),
vol.Optional(ATTR_DISCOVERY, default=list): vol.Any([], SCHEMA_DISCOVERY),
SCHEMA_SERVICES_CONFIG = vol.Schema({
vol.Optional(SERVICE_MQTT, default=dict): schema_or(SCHEMA_CONFIG_MQTT),
}, extra=vol.REMOVE_EXTRA)
DISCOVERY_SERVICES = {
SERVICE_MQTT: SCHEMA_SERVICE_MQTT,
}

View File

@ -1,4 +1,4 @@
"""Represent a snapshot file."""
"""Representation of a snapshot file."""
import asyncio
from base64 import b64decode, b64encode
import json
@ -29,7 +29,7 @@ _LOGGER = logging.getLogger(__name__)
class Snapshot(CoreSysAttributes):
"""A signle hassio snapshot."""
"""A single Hass.io snapshot."""
def __init__(self, coresys, tar_file):
"""Initialize a snapshot."""
@ -72,7 +72,7 @@ class Snapshot(CoreSysAttributes):
@property
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]
@property
@ -92,12 +92,12 @@ class Snapshot(CoreSysAttributes):
@property
def homeassistant_version(self):
"""Return snapshot homeassistant version."""
"""Return snapshot Home Assistant version."""
return self._data[ATTR_HOMEASSISTANT].get(ATTR_VERSION)
@property
def homeassistant(self):
"""Return snapshot homeassistant data."""
"""Return snapshot Home Assistant data."""
return self._data[ATTR_HOMEASSISTANT]
@property
@ -119,7 +119,7 @@ class Snapshot(CoreSysAttributes):
def new(self, slug, name, date, sys_type, password=None):
"""Initialize a new snapshot."""
# init metadata
# Init metadata
self._data[ATTR_SLUG] = slug
self._data[ATTR_NAME] = name
self._data[ATTR_DATE] = date
@ -306,16 +306,16 @@ class Snapshot(CoreSysAttributes):
await asyncio.wait(tasks)
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)
def _folder_save(name):
"""Intenal function to snapshot a folder."""
"""Internal function to snapshot a folder."""
slug_name = name.replace("/", "_")
tar_name = Path(self._tmp.name, f"{slug_name}.tar.gz")
origin_dir = Path(self.sys_config.path_hassio, name)
# Check if exsits
# Check if exists
if not origin_dir.is_dir():
_LOGGER.warning("Can't find snapshot folder %s", name)
return
@ -338,7 +338,7 @@ class Snapshot(CoreSysAttributes):
await asyncio.wait(tasks)
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)
def _folder_restore(name):
@ -356,7 +356,7 @@ class Snapshot(CoreSysAttributes):
if origin_dir.is_dir():
remove_folder(origin_dir)
# Performe a restore
# Perform a restore
try:
_LOGGER.info("Restore folder %s", name)
with SecureTarFile(tar_name, 'r', key=self._key) as tar_file:
@ -372,7 +372,7 @@ class Snapshot(CoreSysAttributes):
await asyncio.wait(tasks)
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_WATCHDOG] = self.sys_homeassistant.watchdog
self.homeassistant[ATTR_BOOT] = self.sys_homeassistant.boot
@ -393,7 +393,7 @@ class Snapshot(CoreSysAttributes):
self._encrypt_data(self.sys_homeassistant.api_password)
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.boot = self.homeassistant[ATTR_BOOT]
self.sys_homeassistant.wait_boot = self.homeassistant[ATTR_WAIT_BOOT]

View File

@ -1,4 +1,4 @@
"""Util addons functions."""
"""Util add-on functions."""
import hashlib
import shutil
import re

View File

@ -1,5 +1,4 @@
"""Validate some things around restore."""
import voluptuous as vol
from ..const import (

28
hassio/utils/validate.py Normal file
View 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

View File

@ -3,15 +3,17 @@ import uuid
import re
import voluptuous as vol
import pytz
from .const import (
ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_CHANNEL, ATTR_TIMEZONE, ATTR_HASSOS,
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_ACCESS_TOKEN,
ATTR_ACCESS_TOKEN, ATTR_DISCOVERY, ATTR_ADDON, ATTR_COMPONENT,
ATTR_PLATFORM, ATTR_SERVICE,
SERVICE_MQTT,
CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV)
from .utils.validate import schema_or, validate_timezone
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{}]+$")
ALSA_DEVICE = vol.Maybe(vol.Match(r"\d+,\d+"))
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):
@ -40,19 +44,6 @@ def validate_repository(repository):
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
def convert_to_docker_ports(data):
"""Convert data into Docker port list."""
@ -83,8 +74,7 @@ DOCKER_PORTS = vol.Schema({
# pylint: disable=no-value-for-parameter
SCHEMA_HASS_CONFIG = vol.Schema({
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex):
vol.Match(r"^[0-9a-f]{32}$"),
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex): UUID_MATCH,
vol.Optional(ATTR_ACCESS_TOKEN): vol.Match(r"^[0-9a-f]{64}$"),
vol.Optional(ATTR_BOOT, default=True): vol.Boolean(),
vol.Inclusive(ATTR_IMAGE, 'custom_hass'): DOCKER_IMAGE,
@ -117,3 +107,19 @@ SCHEMA_HASSIO_CONFIG = vol.Schema({
]): REPOSITORIES,
vol.Optional(ATTR_WAIT_BOOT, default=5): WAIT_BOOT,
}, 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