mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-21 08:06:30 +00:00
Add support to read Host DNS (#1255)
* Add support to read Host DNS * Include properties * Improve host info handling * Add API * Better abstraction * Change prio list * Address lint * fix get properties * Fix nameserver list * Small cleanups * Bit more stability * cleanup
This commit is contained in:
parent
3e69b04b86
commit
ce5183ce16
3
API.md
3
API.md
@ -753,7 +753,8 @@ return:
|
||||
"host": "ip-address",
|
||||
"version": "1",
|
||||
"latest_version": "2",
|
||||
"servers": ["dns://8.8.8.8"]
|
||||
"servers": ["dns://8.8.8.8"],
|
||||
"locals": ["dns://xy"]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -12,9 +12,10 @@ from ..const import (
|
||||
ATTR_CPU_PERCENT,
|
||||
ATTR_HOST,
|
||||
ATTR_LATEST_VERSION,
|
||||
ATTR_LOCALS,
|
||||
ATTR_MEMORY_LIMIT,
|
||||
ATTR_MEMORY_USAGE,
|
||||
ATTR_MEMORY_PERCENT,
|
||||
ATTR_MEMORY_USAGE,
|
||||
ATTR_NETWORK_RX,
|
||||
ATTR_NETWORK_TX,
|
||||
ATTR_SERVERS,
|
||||
@ -45,6 +46,7 @@ class APICoreDNS(CoreSysAttributes):
|
||||
ATTR_LATEST_VERSION: self.sys_dns.latest_version,
|
||||
ATTR_HOST: str(self.sys_docker.network.dns),
|
||||
ATTR_SERVERS: self.sys_dns.servers,
|
||||
ATTR_LOCALS: self.sys_host.network.dns_servers,
|
||||
}
|
||||
|
||||
@api_process
|
||||
|
@ -218,6 +218,7 @@ ATTR_DEBUG = "debug"
|
||||
ATTR_DEBUG_BLOCK = "debug_block"
|
||||
ATTR_DNS = "dns"
|
||||
ATTR_SERVERS = "servers"
|
||||
ATTR_LOCALS = "locals"
|
||||
ATTR_UDEV = "udev"
|
||||
|
||||
PROVIDE_SERVICE = "provide"
|
||||
|
@ -30,15 +30,15 @@ class HassIO(CoreSysAttributes):
|
||||
|
||||
async def setup(self):
|
||||
"""Setup HassIO orchestration."""
|
||||
# Load CoreDNS
|
||||
await self.sys_dns.load()
|
||||
|
||||
# Load DBus
|
||||
await self.sys_dbus.load()
|
||||
|
||||
# Load Host
|
||||
await self.sys_host.load()
|
||||
|
||||
# Load CoreDNS
|
||||
await self.sys_dns.load()
|
||||
|
||||
# Load Home Assistant
|
||||
await self.sys_homeassistant.load()
|
||||
|
||||
|
@ -4,6 +4,7 @@ import logging
|
||||
from .systemd import Systemd
|
||||
from .hostname import Hostname
|
||||
from .rauc import Rauc
|
||||
from .nmi_dns import NMIDnsManager
|
||||
from ..coresys import CoreSysAttributes, CoreSys
|
||||
from ..exceptions import DBusNotConnectedError
|
||||
|
||||
@ -20,6 +21,7 @@ class DBusManager(CoreSysAttributes):
|
||||
self._systemd: Systemd = Systemd()
|
||||
self._hostname: Hostname = Hostname()
|
||||
self._rauc: Rauc = Rauc()
|
||||
self._nmi_dns: NMIDnsManager = NMIDnsManager()
|
||||
|
||||
@property
|
||||
def systemd(self) -> Systemd:
|
||||
@ -36,6 +38,11 @@ class DBusManager(CoreSysAttributes):
|
||||
"""Return the rauc interface."""
|
||||
return self._rauc
|
||||
|
||||
@property
|
||||
def nmi_dns(self) -> NMIDnsManager:
|
||||
"""Return NetworkManager DNS interface."""
|
||||
return self._nmi_dns
|
||||
|
||||
async def load(self) -> None:
|
||||
"""Connect interfaces to D-Bus."""
|
||||
|
||||
@ -43,6 +50,7 @@ class DBusManager(CoreSysAttributes):
|
||||
await self.systemd.connect()
|
||||
await self.hostname.connect()
|
||||
await self.rauc.connect()
|
||||
await self.nmi_dns.connect()
|
||||
except DBusNotConnectedError:
|
||||
_LOGGER.error(
|
||||
"No DBus support from Host. Disabled any kind of host control!"
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""D-Bus interface for hostname."""
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from .interface import DBusInterface
|
||||
from .utils import dbus_connected
|
||||
@ -15,6 +16,15 @@ DBUS_OBJECT = "/org/freedesktop/hostname1"
|
||||
class Hostname(DBusInterface):
|
||||
"""Handle D-Bus interface for hostname/system."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize Properties."""
|
||||
self._hostname: Optional[str] = None
|
||||
self._chassis: Optional[str] = None
|
||||
self._deployment: Optional[str] = None
|
||||
self._kernel: Optional[str] = None
|
||||
self._operating_system: Optional[str] = None
|
||||
self._cpe: Optional[str] = None
|
||||
|
||||
async def connect(self):
|
||||
"""Connect to system's D-Bus."""
|
||||
try:
|
||||
@ -26,6 +36,36 @@ class Hostname(DBusInterface):
|
||||
"No hostname support on the host. Hostname functions have been disabled."
|
||||
)
|
||||
|
||||
@property
|
||||
def hostname(self) -> Optional[str]:
|
||||
"""Return local hostname."""
|
||||
return self._hostname
|
||||
|
||||
@property
|
||||
def chassis(self) -> Optional[str]:
|
||||
"""Return local chassis type."""
|
||||
return self._chassis
|
||||
|
||||
@property
|
||||
def deployment(self) -> Optional[str]:
|
||||
"""Return local deployment type."""
|
||||
return self._deployment
|
||||
|
||||
@property
|
||||
def kernel(self) -> Optional[str]:
|
||||
"""Return local kernel version."""
|
||||
return self._kernel
|
||||
|
||||
@property
|
||||
def operating_system(self) -> Optional[str]:
|
||||
"""Return local operating system."""
|
||||
return self._operating_system
|
||||
|
||||
@property
|
||||
def cpe(self) -> Optional[str]:
|
||||
"""Return local CPE."""
|
||||
return self._cpe
|
||||
|
||||
@dbus_connected
|
||||
def set_static_hostname(self, hostname):
|
||||
"""Change local hostname.
|
||||
@ -35,9 +75,16 @@ class Hostname(DBusInterface):
|
||||
return self.dbus.SetStaticHostname(hostname, False)
|
||||
|
||||
@dbus_connected
|
||||
def get_properties(self):
|
||||
"""Return local host informations.
|
||||
async def update(self):
|
||||
"""Update Properties."""
|
||||
data = await self.dbus.get_properties(DBUS_NAME)
|
||||
if not data:
|
||||
_LOGGER.warning("Can't get properties for Hostname")
|
||||
return
|
||||
|
||||
Return a coroutine.
|
||||
"""
|
||||
return self.dbus.get_properties(DBUS_NAME)
|
||||
self._hostname = data.get("StaticHostname")
|
||||
self._chassis = data.get("Chassis")
|
||||
self._deployment = data.get("Deployment")
|
||||
self._kernel = data.get("KernelRelease")
|
||||
self._operating_system = data.get("OperatingSystemPrettyName")
|
||||
self._cpe = data.get("OperatingSystemCPEName")
|
||||
|
@ -1,12 +1,13 @@
|
||||
"""Interface class for D-Bus wrappers."""
|
||||
from typing import Optional
|
||||
|
||||
from ..utils.gdbus import DBus
|
||||
|
||||
|
||||
class DBusInterface:
|
||||
"""Handle D-Bus interface for hostname/system."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize systemd."""
|
||||
self.dbus = None
|
||||
dbus: Optional[DBus] = None
|
||||
|
||||
@property
|
||||
def is_connected(self):
|
||||
|
85
hassio/dbus/nmi_dns.py
Normal file
85
hassio/dbus/nmi_dns.py
Normal file
@ -0,0 +1,85 @@
|
||||
"""D-Bus interface for hostname."""
|
||||
import logging
|
||||
from typing import Optional, List
|
||||
|
||||
import attr
|
||||
|
||||
from .interface import DBusInterface
|
||||
from .utils import dbus_connected
|
||||
from ..exceptions import DBusError, DBusInterfaceError
|
||||
from ..utils.gdbus import DBus
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
DBUS_NAME = "org.freedesktop.NetworkManager"
|
||||
DBUS_OBJECT = "/org/freedesktop/NetworkManager/DnsManager"
|
||||
|
||||
|
||||
@attr.s
|
||||
class DNSConfiguration:
|
||||
"""NMI DnsManager configuration Object."""
|
||||
|
||||
nameservers: List[str] = attr.ib()
|
||||
domains: List[str] = attr.ib()
|
||||
interface: str = attr.ib()
|
||||
priority: int = attr.ib()
|
||||
vpn: bool = attr.ib()
|
||||
|
||||
|
||||
class NMIDnsManager(DBusInterface):
|
||||
"""Handle D-Bus interface for NMI DnsManager."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize Properties."""
|
||||
self._mode: Optional[str] = None
|
||||
self._rc_manager: Optional[str] = None
|
||||
self._configuration: List[DNSConfiguration] = []
|
||||
|
||||
@property
|
||||
def mode(self) -> Optional[str]:
|
||||
"""Return Propertie mode."""
|
||||
return self._mode
|
||||
|
||||
@property
|
||||
def rc_manager(self) -> Optional[str]:
|
||||
"""Return Propertie RcManager."""
|
||||
return self._rc_manager
|
||||
|
||||
@property
|
||||
def configuration(self) -> List[DNSConfiguration]:
|
||||
"""Return Propertie configuraton."""
|
||||
return self._configuration
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""Connect to system's D-Bus."""
|
||||
try:
|
||||
self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT)
|
||||
except DBusError:
|
||||
_LOGGER.warning("Can't connect to DnsManager")
|
||||
except DBusInterfaceError:
|
||||
_LOGGER.warning(
|
||||
"No DnsManager support on the host. Local DNS functions have been disabled."
|
||||
)
|
||||
|
||||
@dbus_connected
|
||||
async def update(self):
|
||||
"""Update Properties."""
|
||||
data = await self.dbus.get_properties(f"{DBUS_NAME}.DnsManager")
|
||||
if not data:
|
||||
_LOGGER.warning("Can't get properties for NMI DnsManager")
|
||||
return
|
||||
|
||||
self._mode = data.get("Mode")
|
||||
self._rc_manager = data.get("RcManager")
|
||||
|
||||
# Parse configuraton
|
||||
self._configuration.clear()
|
||||
for config in data.get("Configuration", []):
|
||||
dns = DNSConfiguration(
|
||||
config.get("nameservers"),
|
||||
config.get("domains"),
|
||||
config.get("interface"),
|
||||
config.get("priority"),
|
||||
config.get("vpn"),
|
||||
)
|
||||
self._configuration.append(dns)
|
@ -1,22 +1,23 @@
|
||||
"""Home Assistant control object."""
|
||||
import asyncio
|
||||
import logging
|
||||
from contextlib import suppress
|
||||
from ipaddress import IPv4Address
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from string import Template
|
||||
from typing import Awaitable, List, Optional
|
||||
|
||||
import attr
|
||||
import voluptuous as vol
|
||||
|
||||
from .const import ATTR_SERVERS, ATTR_VERSION, DNS_SERVERS, FILE_HASSIO_DNS, DNS_SUFFIX
|
||||
from .const import ATTR_SERVERS, ATTR_VERSION, DNS_SERVERS, DNS_SUFFIX, FILE_HASSIO_DNS
|
||||
from .coresys import CoreSys, CoreSysAttributes
|
||||
from .docker.dns import DockerDNS
|
||||
from .docker.stats import DockerStats
|
||||
from .exceptions import CoreDNSError, CoreDNSUpdateError, DockerAPIError
|
||||
from .misc.forwarder import DNSForward
|
||||
from .utils.json import JsonConfig
|
||||
from .validate import SCHEMA_DNS_CONFIG
|
||||
from .validate import DNS_URL, SCHEMA_DNS_CONFIG
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@ -212,8 +213,17 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
|
||||
_LOGGER.error("Can't read coredns template file: %s", err)
|
||||
raise CoreDNSError() from None
|
||||
|
||||
# Prepare DNS serverlist: Prio 1 Local, Prio 2 Manual, Prio 3 Fallback
|
||||
dns_servers = []
|
||||
for server in self.sys_host.network.dns_servers + self.servers + DNS_SERVERS:
|
||||
try:
|
||||
DNS_URL(server)
|
||||
if server not in dns_servers:
|
||||
dns_servers.append(server)
|
||||
except vol.Invalid:
|
||||
_LOGGER.warning("Ignore invalid DNS Server: %s", server)
|
||||
|
||||
# Generate config file
|
||||
dns_servers = self.servers + list(set(DNS_SERVERS) - set(self.servers))
|
||||
data = corefile_template.safe_substitute(servers=" ".join(dns_servers))
|
||||
|
||||
try:
|
||||
|
@ -7,6 +7,7 @@ from .apparmor import AppArmorControl
|
||||
from .control import SystemControl
|
||||
from .info import InfoCenter
|
||||
from .services import ServiceManager
|
||||
from .network import NetworkManager
|
||||
from ..const import (
|
||||
FEATURES_REBOOT,
|
||||
FEATURES_SHUTDOWN,
|
||||
@ -14,7 +15,7 @@ from ..const import (
|
||||
FEATURES_SERVICES,
|
||||
FEATURES_HASSOS,
|
||||
)
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..coresys import CoreSysAttributes, CoreSys
|
||||
from ..exceptions import HassioError
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
@ -23,40 +24,47 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
class HostManager(CoreSysAttributes):
|
||||
"""Manage supported function from host."""
|
||||
|
||||
def __init__(self, coresys):
|
||||
def __init__(self, coresys: CoreSys):
|
||||
"""Initialize Host manager."""
|
||||
self.coresys = coresys
|
||||
self._alsa = AlsaAudio(coresys)
|
||||
self._apparmor = AppArmorControl(coresys)
|
||||
self._control = SystemControl(coresys)
|
||||
self._info = InfoCenter(coresys)
|
||||
self._services = ServiceManager(coresys)
|
||||
self.coresys: CoreSys = coresys
|
||||
|
||||
self._alsa: AlsaAudio = AlsaAudio(coresys)
|
||||
self._apparmor: AppArmorControl = AppArmorControl(coresys)
|
||||
self._control: SystemControl = SystemControl(coresys)
|
||||
self._info: InfoCenter = InfoCenter(coresys)
|
||||
self._services: ServiceManager = ServiceManager(coresys)
|
||||
self._network: NetworkManager = NetworkManager(coresys)
|
||||
|
||||
@property
|
||||
def alsa(self):
|
||||
def alsa(self) -> AlsaAudio:
|
||||
"""Return host ALSA handler."""
|
||||
return self._alsa
|
||||
|
||||
@property
|
||||
def apparmor(self):
|
||||
def apparmor(self) -> AppArmorControl:
|
||||
"""Return host AppArmor handler."""
|
||||
return self._apparmor
|
||||
|
||||
@property
|
||||
def control(self):
|
||||
def control(self) -> SystemControl:
|
||||
"""Return host control handler."""
|
||||
return self._control
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
def info(self) -> InfoCenter:
|
||||
"""Return host info handler."""
|
||||
return self._info
|
||||
|
||||
@property
|
||||
def services(self):
|
||||
def services(self) -> ServiceManager:
|
||||
"""Return host services handler."""
|
||||
return self._services
|
||||
|
||||
@property
|
||||
def network(self) -> NetworkManager:
|
||||
"""Return host NetworkManager handler."""
|
||||
return self._network
|
||||
|
||||
@property
|
||||
def supperted_features(self):
|
||||
"""Return a list of supported host features."""
|
||||
@ -81,6 +89,9 @@ class HostManager(CoreSysAttributes):
|
||||
if self.sys_dbus.systemd.is_connected:
|
||||
await self.services.update()
|
||||
|
||||
if self.sys_dbus.nmi_dns.is_connected:
|
||||
await self.network.update()
|
||||
|
||||
async def load(self):
|
||||
"""Load host information."""
|
||||
with suppress(HassioError):
|
||||
|
@ -1,8 +1,9 @@
|
||||
"""Info control for host."""
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..exceptions import HassioError, HostNotSupportedError
|
||||
from ..exceptions import HostNotSupportedError, DBusNotConnectedError, DBusError
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@ -13,46 +14,44 @@ class InfoCenter(CoreSysAttributes):
|
||||
def __init__(self, coresys):
|
||||
"""Initialize system center handling."""
|
||||
self.coresys = coresys
|
||||
self._data = {}
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
def hostname(self) -> Optional[str]:
|
||||
"""Return local hostname."""
|
||||
return self._data.get("StaticHostname") or None
|
||||
return self.sys_dbus.hostname.hostname
|
||||
|
||||
@property
|
||||
def chassis(self):
|
||||
def chassis(self) -> Optional[str]:
|
||||
"""Return local chassis type."""
|
||||
return self._data.get("Chassis") or None
|
||||
return self.sys_dbus.hostname.chassis
|
||||
|
||||
@property
|
||||
def deployment(self):
|
||||
def deployment(self) -> Optional[str]:
|
||||
"""Return local deployment type."""
|
||||
return self._data.get("Deployment") or None
|
||||
return self.sys_dbus.hostname.deployment
|
||||
|
||||
@property
|
||||
def kernel(self):
|
||||
def kernel(self) -> Optional[str]:
|
||||
"""Return local kernel version."""
|
||||
return self._data.get("KernelRelease") or None
|
||||
return self.sys_dbus.hostname.kernel
|
||||
|
||||
@property
|
||||
def operating_system(self):
|
||||
def operating_system(self) -> Optional[str]:
|
||||
"""Return local operating system."""
|
||||
return self._data.get("OperatingSystemPrettyName") or None
|
||||
return self.sys_dbus.hostname.operating_system
|
||||
|
||||
@property
|
||||
def cpe(self):
|
||||
def cpe(self) -> Optional[str]:
|
||||
"""Return local CPE."""
|
||||
return self._data.get("OperatingSystemCPEName") or None
|
||||
return self.sys_dbus.hostname.cpe
|
||||
|
||||
async def update(self):
|
||||
"""Update properties over dbus."""
|
||||
if not self.sys_dbus.hostname.is_connected:
|
||||
_LOGGER.error("No hostname D-Bus connection available")
|
||||
raise HostNotSupportedError()
|
||||
|
||||
_LOGGER.info("Update local host information")
|
||||
try:
|
||||
self._data = await self.sys_dbus.hostname.get_properties()
|
||||
except HassioError:
|
||||
await self.sys_dbus.hostname.update()
|
||||
except DBusError:
|
||||
_LOGGER.warning("Can't update host system information!")
|
||||
except DBusNotConnectedError:
|
||||
_LOGGER.error("No hostname D-Bus connection available")
|
||||
raise HostNotSupportedError() from None
|
||||
|
39
hassio/host/network.py
Normal file
39
hassio/host/network.py
Normal file
@ -0,0 +1,39 @@
|
||||
"""Info control for host."""
|
||||
import logging
|
||||
from typing import List, Set
|
||||
|
||||
from ..coresys import CoreSysAttributes, CoreSys
|
||||
from ..exceptions import HostNotSupportedError, DBusNotConnectedError, DBusError
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NetworkManager(CoreSysAttributes):
|
||||
"""Handle local network setup."""
|
||||
|
||||
def __init__(self, coresys: CoreSys):
|
||||
"""Initialize system center handling."""
|
||||
self.coresys: CoreSys = coresys
|
||||
|
||||
@property
|
||||
def dns_servers(self) -> List[str]:
|
||||
"""Return a list of local DNS servers."""
|
||||
# Read all local dns servers
|
||||
servers: Set[str] = set()
|
||||
for config in self.sys_dbus.nmi_dns.configuration:
|
||||
if config.vpn or not config.nameservers:
|
||||
continue
|
||||
servers |= set(config.nameservers)
|
||||
|
||||
return [f"dns://{server}" for server in servers]
|
||||
|
||||
async def update(self):
|
||||
"""Update properties over dbus."""
|
||||
_LOGGER.info("Update local network DNS information")
|
||||
try:
|
||||
await self.sys_dbus.nmi_dns.update()
|
||||
except DBusError:
|
||||
_LOGGER.warning("Can't update host DNS system information!")
|
||||
except DBusNotConnectedError:
|
||||
_LOGGER.error("No hostname D-Bus connection available")
|
||||
raise HostNotSupportedError() from None
|
@ -80,15 +80,13 @@ class DBus:
|
||||
INTROSPECT.format(bus=self.bus_name, object=self.object_path)
|
||||
)
|
||||
|
||||
# Ask data
|
||||
_LOGGER.debug("Introspect %s on %s", self.bus_name, self.object_path)
|
||||
data = await self._send(command)
|
||||
|
||||
# Parse XML
|
||||
data = await self._send(command)
|
||||
try:
|
||||
xml = ET.fromstring(data)
|
||||
except ET.ParseError as err:
|
||||
_LOGGER.error("Can't parse introspect data: %s", err)
|
||||
_LOGGER.debug("Introspect %s on %s", self.bus_name, self.object_path)
|
||||
raise DBusParseError() from None
|
||||
|
||||
# Read available methods
|
||||
|
Loading…
x
Reference in New Issue
Block a user