from typing import TYPE_CHECKING
import copy
if TYPE_CHECKING:
from render import Render
try:
from .error import RenderError
from .validations import valid_portal_scheme_or_raise, valid_http_path_or_raise, valid_port_or_raise
except ImportError:
from error import RenderError
from validations import valid_portal_scheme_or_raise, valid_http_path_or_raise, valid_port_or_raise
class Portals:
def __init__(self, render_instance: "Render"):
self._render_instance = render_instance
self._portals: set[Portal] = set()
def add(self, port: dict, config: dict | None = None):
config = copy.deepcopy((config or {}))
port = copy.deepcopy((port or {}))
# If its not published, portal does not make sense
if port.get("bind_mode", "") != "published":
return
name = config.get("name", "Web UI")
if name in [p._name for p in self._portals]:
raise RenderError(f"Portal [{name}] already added")
host = config.get("host", None)
host_ips = port.get("host_ips", [])
if not isinstance(host_ips, list):
raise RenderError("Expected [host_ips] to be a list of strings")
# Remove wildcard IPs
if "::" in host_ips:
host_ips.remove("::")
if "0.0.0.0" in host_ips:
host_ips.remove("0.0.0.0")
# If host is not set, use the first host_ip (if it exists)
if not host and len(host_ips) >= 1:
host = host_ips[0]
config["host"] = host
if not config.get("port"):
config["port"] = port.get("port_number", 0)
self._portals.add(Portal(name, config))
def render(self):
return [p.render() for _, p in sorted([(p._name, p) for p in self._portals])]
class Portal:
def __init__(self, name: str, config: dict):
self._name = name
self._scheme = valid_portal_scheme_or_raise(config.get("scheme", "http"))
self._host = config.get("host", "0.0.0.0") or "0.0.0.0"
self._port = valid_port_or_raise(config.get("port", 0))
self._path = valid_http_path_or_raise(config.get("path", "/"))
def render(self):
return {
"name": self._name,
"scheme": self._scheme,
"host": self._host,
"port": self._port,
"path": self._path,
}