mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-25 18:16:32 +00:00
commit
7f9232d2b9
3
API.md
3
API.md
@ -512,7 +512,8 @@ Get all available addons.
|
|||||||
"ip_address": "ip address",
|
"ip_address": "ip address",
|
||||||
"ingress": "bool",
|
"ingress": "bool",
|
||||||
"ingress_entry": "null|/api/hassio_ingress/slug",
|
"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"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -431,9 +431,15 @@ class Addon(CoreSysAttributes):
|
|||||||
return f"{proto}://[HOST]:{port}{s_suffix}"
|
return f"{proto}://[HOST]:{port}{s_suffix}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ingress_internal(self):
|
def ingress_port(self):
|
||||||
"""Return Ingress host URL."""
|
"""Return Ingress port."""
|
||||||
return f"http://{self.ip_address}:{self._mesh[ATTR_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
|
@property
|
||||||
def host_network(self):
|
def host_network(self):
|
||||||
|
@ -148,7 +148,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
|
|||||||
vol.Optional(ATTR_WEBUI):
|
vol.Optional(ATTR_WEBUI):
|
||||||
vol.Match(r"^(?:https?|\[PROTO:\w+\]):\/\/\[HOST\]:\[PORT:\d+\].*$"),
|
vol.Match(r"^(?:https?|\[PROTO:\w+\]):\/\/\[HOST\]:\[PORT:\d+\].*$"),
|
||||||
vol.Optional(ATTR_INGRESS, default=False): vol.Boolean(),
|
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_INGRESS_ENTRY): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_HOMEASSISTANT): vol.Maybe(vol.Coerce(str)),
|
vol.Optional(ATTR_HOMEASSISTANT): vol.Maybe(vol.Coerce(str)),
|
||||||
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
|
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
|
||||||
|
@ -44,6 +44,7 @@ from ..const import (
|
|||||||
ATTR_ICON,
|
ATTR_ICON,
|
||||||
ATTR_INGRESS,
|
ATTR_INGRESS,
|
||||||
ATTR_INGRESS_ENTRY,
|
ATTR_INGRESS_ENTRY,
|
||||||
|
ATTR_INGRESS_PORT,
|
||||||
ATTR_INGRESS_URL,
|
ATTR_INGRESS_URL,
|
||||||
ATTR_INSTALLED,
|
ATTR_INSTALLED,
|
||||||
ATTR_IP_ADDRESS,
|
ATTR_IP_ADDRESS,
|
||||||
@ -223,6 +224,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_INGRESS: addon.with_ingress,
|
ATTR_INGRESS: addon.with_ingress,
|
||||||
ATTR_INGRESS_ENTRY: addon.ingress_entry,
|
ATTR_INGRESS_ENTRY: addon.ingress_entry,
|
||||||
ATTR_INGRESS_URL: addon.ingress_url,
|
ATTR_INGRESS_URL: addon.ingress_url,
|
||||||
|
ATTR_INGRESS_PORT: addon.ingress_port,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@ -43,7 +43,7 @@ class APIIngress(CoreSysAttributes):
|
|||||||
|
|
||||||
def _create_url(self, addon: Addon, path: str) -> str:
|
def _create_url(self, addon: Addon, path: str) -> str:
|
||||||
"""Create URL to container."""
|
"""Create URL to container."""
|
||||||
return f"{addon.ingress_internal}/{path}"
|
return f"http://{addon.ip_address}:{addon.ingress_port}/{path}"
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def create_session(self, request: web.Request) -> Dict[str, Any]:
|
async def create_session(self, request: web.Request) -> Dict[str, Any]:
|
||||||
@ -131,7 +131,12 @@ class APIIngress(CoreSysAttributes):
|
|||||||
):
|
):
|
||||||
# Return Response
|
# Return Response
|
||||||
body = await result.read()
|
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
|
# Stream response
|
||||||
response = web.StreamResponse(status=result.status, headers=headers)
|
response = web.StreamResponse(status=result.status, headers=headers)
|
||||||
@ -156,12 +161,7 @@ def _init_header(
|
|||||||
|
|
||||||
# filter flags
|
# filter flags
|
||||||
for name, value in request.headers.items():
|
for name, value in request.headers.items():
|
||||||
if name in (
|
if name in (hdrs.CONTENT_LENGTH, hdrs.CONTENT_ENCODING, istr(HEADER_TOKEN)):
|
||||||
hdrs.CONTENT_LENGTH,
|
|
||||||
hdrs.CONTENT_TYPE,
|
|
||||||
hdrs.CONTENT_ENCODING,
|
|
||||||
istr(HEADER_TOKEN),
|
|
||||||
):
|
|
||||||
continue
|
continue
|
||||||
headers[name] = value
|
headers[name] = value
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ from pathlib import Path
|
|||||||
from ipaddress import ip_network
|
from ipaddress import ip_network
|
||||||
|
|
||||||
|
|
||||||
HASSIO_VERSION = "154"
|
HASSIO_VERSION = "155"
|
||||||
|
|
||||||
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
|
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
|
||||||
URL_HASSIO_VERSION = "https://s3.amazonaws.com/hassio-version/{channel}.json"
|
URL_HASSIO_VERSION = "https://s3.amazonaws.com/hassio-version/{channel}.json"
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
"""Fetch last versions from webserver."""
|
"""Fetch last versions from webserver."""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Optional
|
import random
|
||||||
import secrets
|
import secrets
|
||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from .addons.addon import Addon
|
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 .coresys import CoreSys, CoreSysAttributes
|
||||||
|
from .utils.dt import utc_from_timestamp, utcnow
|
||||||
from .utils.json import JsonConfig
|
from .utils.json import JsonConfig
|
||||||
from .utils.dt import utcnow, utc_from_timestamp
|
|
||||||
from .validate import SCHEMA_INGRESS_CONFIG
|
from .validate import SCHEMA_INGRESS_CONFIG
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -34,6 +35,11 @@ class Ingress(JsonConfig, CoreSysAttributes):
|
|||||||
"""Return sessions."""
|
"""Return sessions."""
|
||||||
return self._data[ATTR_SESSION]
|
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:
|
async def load(self) -> None:
|
||||||
"""Update internal data."""
|
"""Update internal data."""
|
||||||
self._update_token_list()
|
self._update_token_list()
|
||||||
@ -101,3 +107,14 @@ class Ingress(JsonConfig, CoreSysAttributes):
|
|||||||
self.sessions[session] = valid_until.timestamp()
|
self.sessions[session] = valid_until.timestamp()
|
||||||
|
|
||||||
return True
|
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
|
||||||
|
@ -18,6 +18,7 @@ from .const import (
|
|||||||
ATTR_LAST_VERSION,
|
ATTR_LAST_VERSION,
|
||||||
ATTR_PASSWORD,
|
ATTR_PASSWORD,
|
||||||
ATTR_PORT,
|
ATTR_PORT,
|
||||||
|
ATTR_PORTS,
|
||||||
ATTR_REFRESH_TOKEN,
|
ATTR_REFRESH_TOKEN,
|
||||||
ATTR_SESSION,
|
ATTR_SESSION,
|
||||||
ATTR_SSL,
|
ATTR_SSL,
|
||||||
@ -143,6 +144,13 @@ SCHEMA_AUTH_CONFIG = vol.Schema({SHA256: SHA256})
|
|||||||
|
|
||||||
|
|
||||||
SCHEMA_INGRESS_CONFIG = vol.Schema(
|
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,
|
extra=vol.REMOVE_EXTRA,
|
||||||
)
|
)
|
||||||
|
@ -20,3 +20,22 @@ def test_session_handling(coresys):
|
|||||||
coresys.ingress.sessions[session] = not_valid.timestamp()
|
coresys.ingress.sessions[session] = not_valid.timestamp()
|
||||||
assert not coresys.ingress.validate_session(session)
|
assert not coresys.ingress.validate_session(session)
|
||||||
assert not coresys.ingress.validate_session("invalid 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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user