Support dynamic ingress port (#1015)

* Support dynamic ingress port

* Allow to remeber ports

* Add tests

* Fix schema

* Cleanup handling / speed

* Fix port
This commit is contained in:
Pascal Vizeli 2019-04-07 21:59:21 +02:00 committed by GitHub
parent ead5993f3e
commit c2deabb672
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 63 additions and 10 deletions

3
API.md
View File

@ -512,7 +512,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

@ -431,9 +431,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):

View File

@ -148,7 +148,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
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,
@ -223,6 +224,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

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]:

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

@ -18,6 +18,7 @@ from .const import (
ATTR_LAST_VERSION,
ATTR_PASSWORD,
ATTR_PORT,
ATTR_PORTS,
ATTR_REFRESH_TOKEN,
ATTR_SESSION,
ATTR_SSL,
@ -143,6 +144,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