Compare commits

...

16 Commits
153 ... 156

Author SHA1 Message Date
Pascal Vizeli
754cd64213 Merge pull request #1028 from home-assistant/dev
Release 156
2019-04-10 15:36:52 +02:00
Pascal Vizeli
113b62ee77 Fix protocol handling (#1027) 2019-04-10 15:31:43 +02:00
Pascal Vizeli
d9874c4c3e Panel Refresh v2 (#1026) 2019-04-10 01:49:58 +02:00
Pascal Vizeli
ca44e858c5 Update .gitmodules 2019-04-10 01:31:42 +02:00
Pascal Vizeli
c7ca4de307 Update .gitmodules 2019-04-10 00:27:25 +02:00
Pascal Vizeli
b77146a4e0 Allow to add a description for a port (#1023) 2019-04-09 22:15:23 +02:00
Pascal Vizeli
45b4800378 Bump version 156 2019-04-08 10:22:04 +02:00
Pascal Vizeli
7f9232d2b9 Merge pull request #1020 from home-assistant/dev
Release 155
2019-04-08 10:21:27 +02:00
Pascal Vizeli
d90426f745 Fix content-type response (#1017) 2019-04-07 22:13:31 +02:00
Pascal Vizeli
c2deabb672 Support dynamic ingress port (#1015)
* Support dynamic ingress port

* Allow to remeber ports

* Add tests

* Fix schema

* Cleanup handling / speed

* Fix port
2019-04-07 21:59:21 +02:00
Pascal Vizeli
ead5993f3e Bump version 155 2019-04-07 18:19:59 +02:00
Pascal Vizeli
1bcd74e8fa Merge pull request #1014 from home-assistant/dev
Release 154
2019-04-07 18:18:32 +02:00
Pascal Vizeli
118da3c275 Cleanup last_version with latest_version inside code (#1012)
* Cleanup last_version with latest_version inside code

* Fix property
2019-04-07 15:04:16 +02:00
Pascal Vizeli
d7bb9013d4 Improve add-on rebuild (#1011)
* Check version

* Use image instead next_image
2019-04-07 14:42:07 +02:00
Pascal Vizeli
812c46d82b Fix add-on build / install (#1010) 2019-04-07 13:44:17 +02:00
Pascal Vizeli
c0462b28cd Bump version 154 2019-04-07 00:22:34 +02:00
69 changed files with 313 additions and 144 deletions

4
API.md
View File

@@ -480,6 +480,7 @@ Get all available addons.
"build": "bool",
"options": "{}",
"network": "{}|null",
"network_description": "{}|null",
"host_network": "bool",
"host_pid": "bool",
"host_ipc": "bool",
@@ -512,7 +513,8 @@ Get all available addons.
"ip_address": "ip address",
"ingress": "bool",
"ingress_entry": "null|/api/hassio_ingress/slug",
"ingress_url": "null|/api/hassio_ingress/slug/entry.html"
"ingress_url": "null|/api/hassio_ingress/slug/entry.html",
"ingress_port": "null|int"
}
```

View File

@@ -56,6 +56,7 @@ from ..const import (
ATTR_NETWORK,
ATTR_OPTIONS,
ATTR_PORTS,
ATTR_PORTS_DESCRIPTION,
ATTR_PRIVILEGED,
ATTR_PROTECTED,
ATTR_REPOSITORY,
@@ -322,7 +323,7 @@ class Addon(CoreSysAttributes):
return self._mesh[ATTR_REPOSITORY]
@property
def last_version(self):
def latest_version(self):
"""Return version of add-on."""
if self._id in self._data.cache:
return self._data.cache[self._id][ATTR_VERSION]
@@ -364,10 +365,15 @@ class Addon(CoreSysAttributes):
"""Return list of discoverable components/platforms."""
return self._mesh.get(ATTR_DISCOVERY, [])
@property
def ports_description(self):
"""Return descriptions of ports."""
return self._mesh.get(ATTR_PORTS_DESCRIPTION)
@property
def ports(self):
"""Return ports of add-on."""
if self.host_network or ATTR_PORTS not in self._mesh:
if ATTR_PORTS not in self._mesh:
return None
if not self.is_installed or \
@@ -380,13 +386,15 @@ class Addon(CoreSysAttributes):
"""Set custom ports of add-on."""
if value is None:
self._data.user[self._id].pop(ATTR_NETWORK, None)
else:
new_ports = {}
for container_port, host_port in value.items():
if container_port in self._mesh.get(ATTR_PORTS, {}):
new_ports[container_port] = host_port
return
self._data.user[self._id][ATTR_NETWORK] = new_ports
# Secure map ports to value
new_ports = {}
for container_port, host_port in value.items():
if container_port in self._mesh.get(ATTR_PORTS, {}):
new_ports[container_port] = host_port
self._data.user[self._id][ATTR_NETWORK] = new_ports
@property
def ingress_url(self):
@@ -431,9 +439,15 @@ class Addon(CoreSysAttributes):
return f"{proto}://[HOST]:{port}{s_suffix}"
@property
def ingress_internal(self):
"""Return Ingress host URL."""
return f"http://{self.ip_address}:{self._mesh[ATTR_INGRESS_PORT]}"
def ingress_port(self):
"""Return Ingress port."""
if not self.is_installed or not self.with_ingress:
return None
port = self._mesh[ATTR_INGRESS_PORT]
if port == 0:
return self.sys_ingress.get_dynamic_port(self.slug)
return port
@property
def host_network(self):
@@ -845,11 +859,11 @@ class Addon(CoreSysAttributes):
await self._install_apparmor()
try:
await self.instance.install(self.last_version, self.image_next)
await self.instance.install(self.latest_version, self.image_next)
except DockerAPIError:
raise AddonsError() from None
else:
self._set_install(self.image_next, self.last_version)
self._set_install(self.image_next, self.latest_version)
@check_installed
async def uninstall(self) -> None:
@@ -922,7 +936,7 @@ class Addon(CoreSysAttributes):
@check_installed
async def update(self) -> None:
"""Update add-on."""
if self.last_version == self.version_installed:
if self.latest_version == self.version_installed:
_LOGGER.warning("No update available for add-on %s", self._id)
return
@@ -936,10 +950,10 @@ class Addon(CoreSysAttributes):
# Update instance
last_state = await self.state()
try:
await self.instance.update(self.last_version, self.image_next)
await self.instance.update(self.latest_version, self.image_next)
except DockerAPIError:
raise AddonsError() from None
self._set_update(self.image_next, self.last_version)
self._set_update(self.image_next, self.latest_version)
# Setup/Fix AppArmor profile
await self._install_apparmor()
@@ -977,8 +991,11 @@ class Addon(CoreSysAttributes):
last_state = await self.state()
if not self.need_build:
_LOGGER.error("Can't rebuild a none local build add-on!")
_LOGGER.error("Can't rebuild a image based add-on")
raise AddonsNotSupportedError()
if self.version_installed != self.latest_version:
_LOGGER.error("Version changed, use Update instead Rebuild")
raise AddonsError()
# remove docker container but not addon config
try:
@@ -986,6 +1003,8 @@ class Addon(CoreSysAttributes):
await self.instance.install(self.version_installed)
except DockerAPIError:
raise AddonsError() from None
else:
self._set_update(self.image, self.version_installed)
# restore state
if last_state == STATE_STARTED:

View File

@@ -30,8 +30,8 @@ from ..const import (
ATTR_GPIO,
ATTR_HASSIO_API,
ATTR_HASSIO_ROLE,
ATTR_HOMEASSISTANT_API,
ATTR_HOMEASSISTANT,
ATTR_HOMEASSISTANT_API,
ATTR_HOST_DBUS,
ATTR_HOST_IPC,
ATTR_HOST_NETWORK,
@@ -51,6 +51,7 @@ from ..const import (
ATTR_NETWORK,
ATTR_OPTIONS,
ATTR_PORTS,
ATTR_PORTS_DESCRIPTION,
ATTR_PRIVILEGED,
ATTR_PROTECTED,
ATTR_REPOSITORY,
@@ -81,7 +82,14 @@ from ..const import (
STATE_STOPPED,
)
from ..discovery.validate import valid_discovery_service
from ..validate import ALSA_DEVICE, DOCKER_PORTS, NETWORK_PORT, TOKEN, UUID_MATCH
from ..validate import (
ALSA_DEVICE,
DOCKER_PORTS,
DOCKER_PORTS_DESCRIPTION,
NETWORK_PORT,
TOKEN,
UUID_MATCH,
)
_LOGGER = logging.getLogger(__name__)
@@ -145,10 +153,11 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
vol.Required(ATTR_BOOT):
vol.In([BOOT_AUTO, BOOT_MANUAL]),
vol.Optional(ATTR_PORTS): DOCKER_PORTS,
vol.Optional(ATTR_PORTS_DESCRIPTION): DOCKER_PORTS_DESCRIPTION,
vol.Optional(ATTR_WEBUI):
vol.Match(r"^(?:https?|\[PROTO:\w+\]):\/\/\[HOST\]:\[PORT:\d+\].*$"),
vol.Optional(ATTR_INGRESS, default=False): vol.Boolean(),
vol.Optional(ATTR_INGRESS_PORT, default=8099): NETWORK_PORT,
vol.Optional(ATTR_INGRESS_PORT, default=8099): vol.Any(NETWORK_PORT, vol.Equal(0)),
vol.Optional(ATTR_INGRESS_ENTRY): vol.Coerce(str),
vol.Optional(ATTR_HOMEASSISTANT): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),

View File

@@ -44,6 +44,7 @@ from ..const import (
ATTR_ICON,
ATTR_INGRESS,
ATTR_INGRESS_ENTRY,
ATTR_INGRESS_PORT,
ATTR_INGRESS_URL,
ATTR_INSTALLED,
ATTR_IP_ADDRESS,
@@ -57,6 +58,7 @@ from ..const import (
ATTR_MEMORY_USAGE,
ATTR_NAME,
ATTR_NETWORK,
ATTR_NETWORK_DESCRIPTION,
ATTR_NETWORK_RX,
ATTR_NETWORK_TX,
ATTR_OPTIONS,
@@ -135,7 +137,7 @@ class APIAddons(CoreSysAttributes):
ATTR_NAME: addon.name,
ATTR_SLUG: addon.slug,
ATTR_DESCRIPTON: addon.description,
ATTR_VERSION: addon.last_version,
ATTR_VERSION: addon.latest_version,
ATTR_INSTALLED: addon.version_installed,
ATTR_AVAILABLE: addon.available,
ATTR_DETACHED: addon.is_detached,
@@ -179,7 +181,7 @@ class APIAddons(CoreSysAttributes):
ATTR_VERSION: addon.version_installed,
ATTR_AUTO_UPDATE: addon.auto_update,
ATTR_REPOSITORY: addon.repository,
ATTR_LAST_VERSION: addon.last_version,
ATTR_LAST_VERSION: addon.latest_version,
ATTR_STATE: await addon.state(),
ATTR_PROTECTED: addon.protected,
ATTR_RATING: rating_security(addon),
@@ -193,6 +195,7 @@ class APIAddons(CoreSysAttributes):
ATTR_AVAILABLE: addon.available,
ATTR_BUILD: addon.need_build,
ATTR_NETWORK: addon.ports,
ATTR_NETWORK_DESCRIPTION: addon.ports_description,
ATTR_HOST_NETWORK: addon.host_network,
ATTR_HOST_PID: addon.host_pid,
ATTR_HOST_IPC: addon.host_ipc,
@@ -223,6 +226,7 @@ class APIAddons(CoreSysAttributes):
ATTR_INGRESS: addon.with_ingress,
ATTR_INGRESS_ENTRY: addon.ingress_entry,
ATTR_INGRESS_URL: addon.ingress_url,
ATTR_INGRESS_PORT: addon.ingress_port,
}
@api_process
@@ -315,7 +319,7 @@ class APIAddons(CoreSysAttributes):
"""Update add-on."""
addon = self._extract_addon(request)
if addon.last_version == addon.version_installed:
if addon.latest_version == addon.version_installed:
raise APIError("No update available!")
return asyncio.shield(addon.update())

View File

@@ -63,7 +63,7 @@ class APIHomeAssistant(CoreSysAttributes):
"""Return host information."""
return {
ATTR_VERSION: self.sys_homeassistant.version,
ATTR_LAST_VERSION: self.sys_homeassistant.last_version,
ATTR_LAST_VERSION: self.sys_homeassistant.latest_version,
ATTR_MACHINE: self.sys_homeassistant.machine,
ATTR_IP_ADDRESS: str(self.sys_homeassistant.ip_address),
ATTR_ARCH: self.sys_homeassistant.arch,
@@ -83,7 +83,7 @@ class APIHomeAssistant(CoreSysAttributes):
if ATTR_IMAGE in body and ATTR_LAST_VERSION in body:
self.sys_homeassistant.image = body[ATTR_IMAGE]
self.sys_homeassistant.last_version = body[ATTR_LAST_VERSION]
self.sys_homeassistant.latest_version = body[ATTR_LAST_VERSION]
if ATTR_BOOT in body:
self.sys_homeassistant.boot = body[ATTR_BOOT]
@@ -130,7 +130,7 @@ class APIHomeAssistant(CoreSysAttributes):
async def update(self, request: web.Request) -> None:
"""Update Home Assistant."""
body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.sys_homeassistant.last_version)
version = body.get(ATTR_VERSION, self.sys_homeassistant.latest_version)
await asyncio.shield(self.sys_homeassistant.update(version))

View File

@@ -43,7 +43,7 @@ class APIIngress(CoreSysAttributes):
def _create_url(self, addon: Addon, path: str) -> str:
"""Create URL to container."""
return f"{addon.ingress_internal}/{path}"
return f"http://{addon.ip_address}:{addon.ingress_port}/{path}"
@api_process
async def create_session(self, request: web.Request) -> Dict[str, Any]:
@@ -85,7 +85,17 @@ class APIIngress(CoreSysAttributes):
self, request: web.Request, addon: Addon, path: str
) -> web.WebSocketResponse:
"""Ingress route for websocket."""
ws_server = web.WebSocketResponse()
if hdrs.SEC_WEBSOCKET_PROTOCOL in request.headers:
req_protocols = [
str(proto.strip())
for proto in request.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",")
]
else:
req_protocols = ()
ws_server = web.WebSocketResponse(
protocols=req_protocols, autoclose=False, autoping=False
)
await ws_server.prepare(request)
# Preparing
@@ -98,7 +108,11 @@ class APIIngress(CoreSysAttributes):
# Start proxy
async with self.sys_websession.ws_connect(
url, headers=source_header
url,
headers=source_header,
protocols=req_protocols,
autoclose=False,
autoping=False,
) as ws_client:
# Proxy requests
await asyncio.wait(
@@ -131,7 +145,12 @@ class APIIngress(CoreSysAttributes):
):
# Return Response
body = await result.read()
return web.Response(headers=headers, status=result.status, body=body)
return web.Response(
headers=headers,
status=result.status,
content_type=result.content_type,
body=body,
)
# Stream response
response = web.StreamResponse(status=result.status, headers=headers)
@@ -156,12 +175,7 @@ def _init_header(
# filter flags
for name, value in request.headers.items():
if name in (
hdrs.CONTENT_LENGTH,
hdrs.CONTENT_TYPE,
hdrs.CONTENT_ENCODING,
istr(HEADER_TOKEN),
):
if name in (hdrs.CONTENT_LENGTH, hdrs.CONTENT_ENCODING, istr(HEADER_TOKEN)):
continue
headers[name] = value
@@ -204,14 +218,17 @@ def _is_websocket(request: web.Request) -> bool:
async def _websocket_forward(ws_from, ws_to):
"""Handle websocket message directly."""
async for msg in ws_from:
if msg.type == aiohttp.WSMsgType.TEXT:
await ws_to.send_str(msg.data)
elif msg.type == aiohttp.WSMsgType.BINARY:
await ws_to.send_bytes(msg.data)
elif msg.type == aiohttp.WSMsgType.PING:
await ws_to.ping()
elif msg.type == aiohttp.WSMsgType.PONG:
await ws_to.pong()
elif ws_to.closed:
await ws_to.close(code=ws_to.close_code, message=msg.extra)
try:
async for msg in ws_from:
if msg.type == aiohttp.WSMsgType.TEXT:
await ws_to.send_str(msg.data)
elif msg.type == aiohttp.WSMsgType.BINARY:
await ws_to.send_bytes(msg.data)
elif msg.type == aiohttp.WSMsgType.PING:
await ws_to.ping()
elif msg.type == aiohttp.WSMsgType.PONG:
await ws_to.pong()
elif ws_to.closed:
await ws_to.close(code=ws_to.close_code, message=msg.extra)
except RuntimeError:
_LOGGER.warning("Ingress Websocket runtime error")

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

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[7],{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(121),i=t.n(e),o=t(123),u=t.n(o),a=i.a,c=u.a}}]);

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
/**
@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.

View File

@@ -1 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.31b41b04602ce627ad98.js","sourceRoot":""}
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.510634470d399e194ace.js","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,21 @@
/**
@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
*/

Binary file not shown.

View File

@@ -1 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.1ac383635811d6c2cb4b.js","sourceRoot":""}
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.564a2f7b1c38ddaa4ce0.js","sourceRoot":""}

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.659084fef4e3b7b66a76.js","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,31 @@
/**
@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
*/

Binary file not shown.

View File

@@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.6e9c87e51920a9c354e5.js","sourceRoot":""}

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -1 +0,0 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[4],{114: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(104),i=t.n(e),o=t(106),u=t.n(o),a=i.a,c=u.a}}]);

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,28 @@
/**
* @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) 2019 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.
@@ -30,20 +55,6 @@ 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 2018 Google Inc. All Rights Reserved.
@@ -122,17 +133,6 @@ and limitations under the License.
* THE SOFTWARE.
*/
/**
@license
Copyright (c) 2019 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) 2018 The Polymer Project Authors. All rights reserved.
@@ -147,17 +147,6 @@ found at http://polymer.github.io/PATENTS.txt
* 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) 2014 The Polymer Project Authors. All rights reserved.
@@ -178,3 +167,14 @@ The complete set of contributors may be found at http://polymer.github.io/CONTRI
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
*/

Binary file not shown.

View File

@@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.a7e5fb452cd1b3a5faef.js","sourceRoot":""}

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

View File

@@ -0,0 +1,10 @@
/**
@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.

View File

@@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk.f15d7f41c0d302cbbc7a.js","sourceRoot":""}

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

View File

@@ -1 +1 @@
!function(e){function n(n){for(var t,o,i=n[0],a=n[1],u=0,f=[];u<i.length;u++)o=i[u],r[o]&&f.push(r[o][0]),r[o]=0;for(t in a)Object.prototype.hasOwnProperty.call(a,t)&&(e[t]=a[t]);for(c&&c(n);f.length;)f.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 i=new Promise(function(n,o){t=r[e]=[n,o]});n.push(t[2]=i);var a,u=document.createElement("script");u.charset="utf-8",u.timeout=120,o.nc&&u.setAttribute("nonce",o.nc),u.src=function(e){return o.p+"chunk."+{0:"1ac383635811d6c2cb4b",2:"381b1e7d41316cfb583c",3:"a6e3bc73416702354e6d",4:"8a4a3a3274af0f09d86b",5:"7589a9f39a552ee63688",6:"31b41b04602ce627ad98",7:"ff45557361d5d6bd46af"}[e]+".js"}(e),a=function(n){u.onerror=u.onload=null,clearTimeout(c);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,a=new Error("Loading chunk "+e+" failed.\n("+o+": "+i+")");a.type=o,a.request=i,t[1](a)}r[e]=void 0}};var c=setTimeout(function(){a({type:"timeout",target:u})},12e4);u.onerror=u.onload=a,document.head.appendChild(u)}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||[],a=i.push.bind(i);i.push=n,i=i.slice();for(var u=0;u<i.length;u++)n(i[u]);var c=a;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(6),t.e(3)]).then(t.bind(null,1))});var r=document.createElement("style");r.innerHTML="\nbody {\n font-family: Roboto, sans-serif;\n -moz-osx-font-smoothing: grayscale;\n -webkit-font-smoothing: antialiased;\n font-weight: 400;\n margin: 0;\n padding: 0;\n height: 100vh;\n}\n",document.head.appendChild(r)}]);
!function(e){function n(n){for(var t,o,a=n[0],i=n[1],c=0,u=[];c<a.length;c++)o=a[c],r[o]&&u.push(r[o][0]),r[o]=0;for(t in i)Object.prototype.hasOwnProperty.call(i,t)&&(e[t]=i[t]);for(f&&f(n);u.length;)u.shift()()}var t={},r={4: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,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:"564a2f7b1c38ddaa4ce0",1:"659084fef4e3b7b66a76",2:"510634470d399e194ace",3:"f15d7f41c0d302cbbc7a",5:"5d31a1778f717ac8b063",6:"feb68d852b3f6d67d970",7:"3a63ad36bccf4ea567fa",8:"a571dfa106202cc57af6",9:"a7e5fb452cd1b3a5faef",10:"b3340b3df270d20af4a1",11:"6e9c87e51920a9c354e5",12:"183d719f6ced86001f8e",13:"739b67c99ab56cdbd75d"}[e]+".js"}(e),i=function(n){c.onerror=c.onload=null,clearTimeout(f);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 f=setTimeout(function(){i({type:"timeout",target:c})},12e4);c.onerror=c.onload=i,document.head.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 c=0;c<a.length;c++)n(a[c]);var f=i;o(o.s=0)}([function(e,n,t){window.loadES5Adapter().then(function(){Promise.all([t.e(1),t.e(5)]).then(t.bind(null,2)),Promise.all([t.e(1),t.e(9),t.e(6)]).then(t.bind(null,1))});var r=document.createElement("style");r.innerHTML="\nbody {\n font-family: Roboto, sans-serif;\n -moz-osx-font-smoothing: grayscale;\n -webkit-font-smoothing: antialiased;\n font-weight: 400;\n margin: 0;\n padding: 0;\n height: 100vh;\n}\n",document.head.appendChild(r)}]);

Binary file not shown.

View File

@@ -74,7 +74,7 @@ class APISupervisor(CoreSysAttributes):
ATTR_SLUG: addon.slug,
ATTR_DESCRIPTON: addon.description,
ATTR_STATE: await addon.state(),
ATTR_VERSION: addon.last_version,
ATTR_VERSION: addon.latest_version,
ATTR_INSTALLED: addon.version_installed,
ATTR_REPOSITORY: addon.repository,
ATTR_ICON: addon.with_icon,

View File

@@ -3,7 +3,7 @@ from pathlib import Path
from ipaddress import ip_network
HASSIO_VERSION = "153"
HASSIO_VERSION = "156"
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
URL_HASSIO_VERSION = "https://s3.amazonaws.com/hassio-version/{channel}.json"
@@ -92,6 +92,7 @@ ATTR_DESCRIPTON = "description"
ATTR_STARTUP = "startup"
ATTR_BOOT = "boot"
ATTR_PORTS = "ports"
ATTR_PORTS_DESCRIPTION = "ports_description"
ATTR_PORT = "port"
ATTR_SSL = "ssl"
ATTR_MAP = "map"
@@ -122,6 +123,7 @@ ATTR_HOST_PID = "host_pid"
ATTR_HOST_IPC = "host_ipc"
ATTR_HOST_DBUS = "host_dbus"
ATTR_NETWORK = "network"
ATTR_NETWORK_DESCRIPTION = "network_description"
ATTR_TMPFS = "tmpfs"
ATTR_PRIVILEGED = "privileged"
ATTR_USER = "user"

View File

@@ -147,7 +147,7 @@ class DockerAddon(DockerInterface):
@property
def ports(self) -> Optional[Dict[str, Union[str, int, None]]]:
"""Filter None from add-on ports."""
if not self.addon.ports:
if self.addon.host_network or not self.addon.ports:
return None
return {
@@ -355,8 +355,8 @@ class DockerAddon(DockerInterface):
"""
if self.addon.need_build:
self._build(tag)
super()._install(tag, image)
else:
super()._install(tag, image)
def _build(self, tag: str) -> None:
"""Build a Docker container.

View File

@@ -164,14 +164,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
return self.instance.version
@property
def last_version(self) -> str:
def latest_version(self) -> str:
"""Return last available version of Home Assistant."""
if self.is_custom_image:
return self._data.get(ATTR_LAST_VERSION)
return self.sys_updater.version_homeassistant
@last_version.setter
def last_version(self, value: str):
@latest_version.setter
def latest_version(self, value: str):
"""Set last available version of Home Assistant."""
if value:
self._data[ATTR_LAST_VERSION] = value
@@ -246,10 +246,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
_LOGGER.info("Setup Home Assistant")
while True:
# read homeassistant tag and install it
if not self.last_version:
if not self.latest_version:
await self.sys_updater.reload()
tag = self.last_version
tag = self.latest_version
if tag:
with suppress(DockerAPIError):
await self.instance.install(tag)
@@ -273,7 +273,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
@process_lock
async def update(self, version=None) -> None:
"""Update HomeAssistant version."""
version = version or self.last_version
version = version or self.latest_version
rollback = self.version if not self.error_state else None
running = await self.instance.is_running()
exists = await self.instance.exists()

View File

@@ -1,14 +1,15 @@
"""Fetch last versions from webserver."""
from datetime import timedelta
import logging
from typing import Dict, Optional
import random
import secrets
from typing import Dict, Optional
from .addons.addon import Addon
from .const import ATTR_SESSION, FILE_HASSIO_INGRESS
from .const import ATTR_PORTS, ATTR_SESSION, FILE_HASSIO_INGRESS
from .coresys import CoreSys, CoreSysAttributes
from .utils.dt import utc_from_timestamp, utcnow
from .utils.json import JsonConfig
from .utils.dt import utcnow, utc_from_timestamp
from .validate import SCHEMA_INGRESS_CONFIG
_LOGGER = logging.getLogger(__name__)
@@ -34,6 +35,11 @@ class Ingress(JsonConfig, CoreSysAttributes):
"""Return sessions."""
return self._data[ATTR_SESSION]
@property
def ports(self) -> Dict[str, int]:
"""Return list of dynamic ports."""
return self._data[ATTR_PORTS]
async def load(self) -> None:
"""Update internal data."""
self._update_token_list()
@@ -101,3 +107,14 @@ class Ingress(JsonConfig, CoreSysAttributes):
self.sessions[session] = valid_until.timestamp()
return True
def get_dynamic_port(self, addon_slug: str) -> int:
"""Get/Create a dynamic port from range."""
if addon_slug in self.ports:
return self.ports[addon_slug]
port = random.randint(62000, 65500)
# Save port for next time
self.ports[addon_slug] = port
self.save_data()
return port

View File

@@ -417,7 +417,7 @@ class Snapshot(CoreSysAttributes):
if self.sys_homeassistant.is_custom_image:
self.homeassistant[ATTR_IMAGE] = self.sys_homeassistant.image
self.homeassistant[ATTR_LAST_VERSION] = \
self.sys_homeassistant.last_version
self.sys_homeassistant.latest_version
# API/Proxy
self.homeassistant[ATTR_PORT] = self.sys_homeassistant.api_port
@@ -436,7 +436,7 @@ class Snapshot(CoreSysAttributes):
# Custom image
if self.homeassistant.get(ATTR_IMAGE):
self.sys_homeassistant.image = self.homeassistant[ATTR_IMAGE]
self.sys_homeassistant.last_version = \
self.sys_homeassistant.latest_version = \
self.homeassistant[ATTR_LAST_VERSION]
# API/Proxy

View File

@@ -49,7 +49,7 @@ class Supervisor(CoreSysAttributes):
@property
def need_update(self) -> bool:
"""Return True if an update is available."""
return self.version != self.last_version
return self.version != self.latest_version
@property
def version(self) -> str:
@@ -57,7 +57,7 @@ class Supervisor(CoreSysAttributes):
return self.instance.version
@property
def last_version(self) -> str:
def latest_version(self) -> str:
"""Return last available version of Home Assistant."""
return self.sys_updater.version_hassio
@@ -101,7 +101,7 @@ class Supervisor(CoreSysAttributes):
async def update(self, version: Optional[str] = None) -> None:
"""Update Home Assistant version."""
version = version or self.last_version
version = version or self.latest_version
if version == self.sys_supervisor.version:
_LOGGER.warning("Version %s is already installed", version)

View File

@@ -93,7 +93,7 @@ class Tasks(CoreSysAttributes):
if not addon.is_installed or not addon.auto_update:
continue
if addon.version_installed == addon.last_version:
if addon.version_installed == addon.latest_version:
continue
if addon.test_update_schema():

View File

@@ -18,6 +18,7 @@ from .const import (
ATTR_LAST_VERSION,
ATTR_PASSWORD,
ATTR_PORT,
ATTR_PORTS,
ATTR_REFRESH_TOKEN,
ATTR_SESSION,
ATTR_SSL,
@@ -60,36 +61,18 @@ def validate_repository(repository):
REPOSITORIES = vol.All([validate_repository], vol.Unique())
# pylint: disable=inconsistent-return-statements
def convert_to_docker_ports(data):
"""Convert data into Docker port list."""
# dynamic ports
if data is None:
return None
# single port
if isinstance(data, int):
return NETWORK_PORT(data)
# port list
if isinstance(data, list) and len(data) > 2:
return vol.Schema([NETWORK_PORT])(data)
# ip port mapping
if isinstance(data, list) and len(data) == 2:
return (vol.Coerce(str)(data[0]), NETWORK_PORT(data[1]))
raise vol.Invalid("Can't validate Docker host settings")
DOCKER_PORTS = vol.Schema(
{
vol.All(
vol.Coerce(str), vol.Match(r"^\d+(?:/tcp|/udp)?$")
): convert_to_docker_ports
vol.All(vol.Coerce(str), vol.Match(r"^\d+(?:/tcp|/udp)?$")): vol.Maybe(
NETWORK_PORT
)
}
)
DOCKER_PORTS_DESCRIPTION = vol.Schema(
{vol.All(vol.Coerce(str), vol.Match(r"^\d+(?:/tcp|/udp)?$")): vol.Coerce(str)}
)
# pylint: disable=no-value-for-parameter
SCHEMA_HASS_CONFIG = vol.Schema(
@@ -143,6 +126,13 @@ SCHEMA_AUTH_CONFIG = vol.Schema({SHA256: SHA256})
SCHEMA_INGRESS_CONFIG = vol.Schema(
{vol.Required(ATTR_SESSION, default=dict): vol.Schema({TOKEN: vol.Coerce(float)})},
{
vol.Required(ATTR_SESSION, default=dict): vol.Schema(
{TOKEN: vol.Coerce(float)}
),
vol.Required(ATTR_PORTS, default=dict): vol.Schema(
{vol.Coerce(str): NETWORK_PORT}
),
},
extra=vol.REMOVE_EXTRA,
)

View File

@@ -20,3 +20,22 @@ def test_session_handling(coresys):
coresys.ingress.sessions[session] = not_valid.timestamp()
assert not coresys.ingress.validate_session(session)
assert not coresys.ingress.validate_session("invalid session")
def test_dynamic_ports(coresys):
"""Test dyanmic port handling."""
port_test1 = coresys.ingress.get_dynamic_port("test1")
assert port_test1
assert coresys.ingress.save_data.called
assert port_test1 == coresys.ingress.get_dynamic_port("test1")
port_test2 = coresys.ingress.get_dynamic_port("test2")
assert port_test2
assert port_test2 != port_test1
assert port_test2 > 62000
assert port_test2 < 65500
assert port_test1 > 62000
assert port_test1 < 65500