mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-23 09:06:29 +00:00
Add hostname function
This commit is contained in:
parent
2a81ced817
commit
0bb81136bb
38
API.md
38
API.md
@ -217,8 +217,11 @@ return:
|
||||
### Host
|
||||
|
||||
- POST `/host/reload`
|
||||
|
||||
- POST `/host/shutdown`
|
||||
|
||||
- POST `/host/reboot`
|
||||
|
||||
- GET `/host/info`
|
||||
|
||||
```json
|
||||
@ -228,14 +231,21 @@ return:
|
||||
"last_version": "",
|
||||
"features": ["shutdown", "reboot", "update", "hostname", "network_info", "network_control"],
|
||||
"hostname": "",
|
||||
"os": "",
|
||||
"audio": {
|
||||
"input": "0,0",
|
||||
"output": "0,0"
|
||||
}
|
||||
"operating_system": "",
|
||||
"kernel": "",
|
||||
"chassis": ""
|
||||
}
|
||||
```
|
||||
|
||||
- POST `/host/options`
|
||||
|
||||
```json
|
||||
{
|
||||
"hostname": "",
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
- POST `/host/update`
|
||||
|
||||
Optional:
|
||||
@ -284,24 +294,6 @@ Optional:
|
||||
}
|
||||
```
|
||||
|
||||
### Network
|
||||
|
||||
- GET `/network/info`
|
||||
|
||||
```json
|
||||
{
|
||||
"hostname": ""
|
||||
}
|
||||
```
|
||||
|
||||
- POST `/network/options`
|
||||
|
||||
```json
|
||||
{
|
||||
"hostname": "",
|
||||
}
|
||||
```
|
||||
|
||||
### Home Assistant
|
||||
|
||||
- GET `/homeassistant/info`
|
||||
|
@ -9,7 +9,6 @@ from .discovery import APIDiscovery
|
||||
from .homeassistant import APIHomeAssistant
|
||||
from .hardware import APIHardware
|
||||
from .host import APIHost
|
||||
from .network import APINetwork
|
||||
from .proxy import APIProxy
|
||||
from .supervisor import APISupervisor
|
||||
from .snapshots import APISnapshots
|
||||
@ -44,7 +43,6 @@ class RestAPI(CoreSysAttributes):
|
||||
self._register_panel()
|
||||
self._register_addons()
|
||||
self._register_snapshots()
|
||||
self._register_network()
|
||||
self._register_discovery()
|
||||
self._register_services()
|
||||
|
||||
@ -61,16 +59,6 @@ class RestAPI(CoreSysAttributes):
|
||||
web.post('/host/reload', api_host.reload),
|
||||
])
|
||||
|
||||
def _register_network(self):
|
||||
"""Register network function."""
|
||||
api_net = APINetwork()
|
||||
api_net.coresys = self.coresys
|
||||
|
||||
self.webapp.add_routes([
|
||||
web.get('/network/info', api_net.info),
|
||||
web.post('/network/options', api_net.options),
|
||||
])
|
||||
|
||||
def _register_hardware(self):
|
||||
"""Register hardware function."""
|
||||
api_hardware = APIHardware()
|
||||
|
@ -7,7 +7,7 @@ import voluptuous as vol
|
||||
from .utils import api_process, api_validate
|
||||
from ..const import (
|
||||
ATTR_VERSION, ATTR_LAST_VERSION, ATTR_TYPE, ATTR_HOSTNAME, ATTR_FEATURES,
|
||||
ATTR_OS)
|
||||
ATTR_OPERATING_SYSTEM, ATTR_KERNEL, ATTR_CHASSIS)
|
||||
from ..coresys import CoreSysAttributes
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -16,6 +16,10 @@ SCHEMA_VERSION = vol.Schema({
|
||||
vol.Optional(ATTR_VERSION): vol.Coerce(str),
|
||||
})
|
||||
|
||||
SCHEMA_OPTIONS = vol.Schema({
|
||||
vol.Optional(ATTR_HOSTNAME): vol.Coerce(str),
|
||||
})
|
||||
|
||||
|
||||
class APIHost(CoreSysAttributes):
|
||||
"""Handle rest api for host functions."""
|
||||
@ -24,14 +28,25 @@ class APIHost(CoreSysAttributes):
|
||||
async def info(self, request):
|
||||
"""Return host information."""
|
||||
return {
|
||||
ATTR_TYPE: ,
|
||||
ATTR_VERSION: ,
|
||||
ATTR_LAST_VERSION: ,
|
||||
ATTR_TYPE: None,
|
||||
ATTR_CHASSIS: self.sys_host.local.chassis,
|
||||
ATTR_VERSION: None,
|
||||
ATTR_LAST_VERSION: None,
|
||||
ATTR_FEATURES: self.sys_host.features,
|
||||
ATTR_HOSTNAME: ,
|
||||
ATTR_OS: ,
|
||||
ATTR_HOSTNAME: self.sys_host.local.hostname,
|
||||
ATTR_OPERATING_SYSTEM: self.sys_host.local.operating_system,
|
||||
ATTR_KERNEL: self.sys_host.local.kernel,
|
||||
}
|
||||
|
||||
@api_process
|
||||
async def options(self, request):
|
||||
"""Edit host settings."""
|
||||
body = await api_validate(SCHEMA_OPTIONS, request)
|
||||
|
||||
# hostname
|
||||
if ATTR_HOSTNAME in body:
|
||||
await self.sys_host.local.set_hostname(body[ATTR_HOSTNAME])
|
||||
|
||||
@api_process
|
||||
def reboot(self, request):
|
||||
"""Reboot host."""
|
||||
|
@ -1,38 +0,0 @@
|
||||
"""Init file for HassIO network rest api."""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from .utils import api_process, api_process_hostcontrol, api_validate
|
||||
from ..const import ATTR_HOSTNAME
|
||||
from ..coresys import CoreSysAttributes
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SCHEMA_OPTIONS = vol.Schema({
|
||||
vol.Optional(ATTR_HOSTNAME): vol.Coerce(str),
|
||||
})
|
||||
|
||||
|
||||
class APINetwork(CoreSysAttributes):
|
||||
"""Handle rest api for network functions."""
|
||||
|
||||
@api_process
|
||||
async def info(self, request):
|
||||
"""Show network settings."""
|
||||
return {
|
||||
ATTR_HOSTNAME: self._host_control.hostname,
|
||||
}
|
||||
|
||||
@api_process_hostcontrol
|
||||
async def options(self, request):
|
||||
"""Edit network settings."""
|
||||
body = await api_validate(SCHEMA_OPTIONS, request)
|
||||
|
||||
# hostname
|
||||
if ATTR_HOSTNAME in body:
|
||||
if self._host_control.hostname != body[ATTR_HOSTNAME]:
|
||||
await self._host_control.set_hostname(body[ATTR_HOSTNAME])
|
||||
|
||||
return True
|
@ -61,7 +61,8 @@ ATTR_LONG_DESCRIPTION = 'long_description'
|
||||
ATTR_HOSTNAME = 'hostname'
|
||||
ATTR_TIMEZONE = 'timezone'
|
||||
ATTR_ARGS = 'args'
|
||||
ATTR_OS = 'os'
|
||||
ATTR_OPERATING_SYSTEM = 'operating_system'
|
||||
ATTR_CHASSIS = 'chassis'
|
||||
ATTR_TYPE = 'type'
|
||||
ATTR_SOURCE = 'source'
|
||||
ATTR_FEATURES = 'features'
|
||||
@ -159,6 +160,7 @@ ATTR_DISCOVERY = 'discovery'
|
||||
ATTR_PROTECTED = 'protected'
|
||||
ATTR_CRYPTO = 'crypto'
|
||||
ATTR_BRANCH = 'branch'
|
||||
ATTR_KERNEL = 'kernel'
|
||||
ATTR_SECCOMP = 'seccomp'
|
||||
ATTR_APPARMOR = 'apparmor'
|
||||
|
||||
@ -213,5 +215,3 @@ FEATURES_SHUTDOWN = 'shutdown'
|
||||
FEATURES_REBOOT = 'reboot'
|
||||
FEATURES_UPDATE = 'update'
|
||||
FEATURES_HOSTNAME = 'hostname'
|
||||
FEATURES_NETWORK_INFO = 'network_info'
|
||||
FEATURES_NETWORK_CONTROL = 'network_control'
|
||||
|
@ -21,3 +21,19 @@ class Hostname(DBusInterface):
|
||||
self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT)
|
||||
except DBusError:
|
||||
_LOGGER.warning("Can't connect to hostname")
|
||||
|
||||
@dbus_connected
|
||||
def set_hostname(self, hostname):
|
||||
"""Change local hostname.
|
||||
|
||||
Return a coroutine.
|
||||
"""
|
||||
return self.dbus.SetHostname(hostname)
|
||||
|
||||
@dbus_connected
|
||||
def get_properties(self):
|
||||
"""Return local host informations.
|
||||
|
||||
Return a coroutine.
|
||||
"""
|
||||
return self.dbus.get_properties(DBUS_NAME)
|
||||
|
@ -1 +0,0 @@
|
||||
"""Interface to NetworkManager over dbus."""
|
@ -1 +0,0 @@
|
||||
"""Interface to Rauc OTA over dbus."""
|
@ -2,7 +2,8 @@
|
||||
|
||||
from .alsa import AlsaAudio
|
||||
from .power import PowerControl
|
||||
from ..const import FEATURES_REBOOT, FEATURES_SHUTDOWN
|
||||
from .local import LocalCenter
|
||||
from ..const import FEATURES_REBOOT, FEATURES_SHUTDOWN, FEATURES_HOSTNAME
|
||||
from ..coresys import CoreSysAttributes
|
||||
|
||||
|
||||
@ -14,6 +15,7 @@ class HostManager(CoreSysAttributes):
|
||||
self.coresys = coresys
|
||||
self._alsa = AlsaAudio(coresys)
|
||||
self._power = PowerControl(coresys)
|
||||
self._local = LocalCenter(coresys)
|
||||
|
||||
@property
|
||||
def alsa(self):
|
||||
@ -25,6 +27,11 @@ class HostManager(CoreSysAttributes):
|
||||
"""Return host power handler."""
|
||||
return self._power
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
"""Return host local handler."""
|
||||
return self._local
|
||||
|
||||
@property
|
||||
def supperted_features(self):
|
||||
"""Return a list of supported host features."""
|
||||
@ -36,12 +43,16 @@ class HostManager(CoreSysAttributes):
|
||||
FEATURES_SHUTDOWN,
|
||||
])
|
||||
|
||||
if self.sys_dbus.hostname.is_connected:
|
||||
features.append(FEATURES_HOSTNAME)
|
||||
|
||||
return features
|
||||
|
||||
async def load(self):
|
||||
"""Load host functions."""
|
||||
pass
|
||||
if self.sys_dbus.hostname.is_connected:
|
||||
await self.local.update()
|
||||
|
||||
async def reload(self):
|
||||
def reload(self):
|
||||
"""Reload host information."""
|
||||
pass
|
||||
return self.load()
|
||||
|
67
hassio/host/local.py
Normal file
67
hassio/host/local.py
Normal file
@ -0,0 +1,67 @@
|
||||
"""Power control for host."""
|
||||
import logging
|
||||
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..exceptions import HassioError, HostNotSupportedError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
UNKNOWN = 'Unknown'
|
||||
|
||||
|
||||
class LocalCenter(CoreSysAttributes):
|
||||
"""Handle local system information controls."""
|
||||
|
||||
def __init__(self, coresys):
|
||||
"""Initialize system center handling."""
|
||||
self.coresys = coresys
|
||||
self._data = {}
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
"""Return local hostname."""
|
||||
return self._data.get('Hostname', UNKNOWN)
|
||||
|
||||
@property
|
||||
def chassis(self):
|
||||
"""Return local chassis type."""
|
||||
return self._data.get('Chassis', UNKNOWN)
|
||||
|
||||
@property
|
||||
def kernel(self):
|
||||
"""Return local kernel version."""
|
||||
return self._data.get('KernelRelease', UNKNOWN)
|
||||
|
||||
@property
|
||||
def operating_system(self):
|
||||
"""Return local operating system."""
|
||||
return self._data.get('OperatingSystemPrettyName', UNKNOWN)
|
||||
|
||||
@property
|
||||
def cpe(self):
|
||||
"""Return local CPE."""
|
||||
return self._data.get('OperatingSystemCPEName', UNKNOWN)
|
||||
|
||||
def _check_dbus(self):
|
||||
"""Check if systemd is connect or raise error."""
|
||||
if not self.sys_dbus.hostname.is_connected:
|
||||
_LOGGER.error("No hostname dbus connection available")
|
||||
raise HostNotSupportedError()
|
||||
|
||||
async def update(self):
|
||||
"""Update properties over dbus."""
|
||||
self._check_dbus()
|
||||
|
||||
_LOGGER.info("Update local host information")
|
||||
try:
|
||||
self._data = await self.sys_dbus.hostname.get_properties()
|
||||
except HassioError:
|
||||
_LOGGER.warning("Can't update host system information!")
|
||||
|
||||
async def set_hostname(self, hostname):
|
||||
"""Set local a new Hostname."""
|
||||
self._check_dbus()
|
||||
|
||||
_LOGGER.info("Set Hostname %s", hostname)
|
||||
await self.sys_dbus.hostname.set_hostname(hostname)
|
||||
await self.update()
|
@ -14,7 +14,7 @@ class PowerControl(CoreSysAttributes):
|
||||
"""Initialize host power handling."""
|
||||
self.coresys = coresys
|
||||
|
||||
def _check_systemd(self):
|
||||
def _check_dbus(self):
|
||||
"""Check if systemd is connect or raise error."""
|
||||
if not self.sys_dbus.systemd.is_connected:
|
||||
_LOGGER.error("No systemd dbus connection available")
|
||||
@ -22,7 +22,7 @@ class PowerControl(CoreSysAttributes):
|
||||
|
||||
async def reboot(self):
|
||||
"""Reboot host system."""
|
||||
self._check_systemd()
|
||||
self._check_dbus()
|
||||
|
||||
_LOGGER.info("Initialize host reboot over systemd")
|
||||
try:
|
||||
@ -32,7 +32,7 @@ class PowerControl(CoreSysAttributes):
|
||||
|
||||
async def shutdown(self):
|
||||
"""Shutdown host system."""
|
||||
self._check_systemd()
|
||||
self._check_dbus()
|
||||
|
||||
_LOGGER.info("Initialize host power off over systemd")
|
||||
try:
|
||||
|
@ -10,15 +10,22 @@ from ..exceptions import DBusFatalError, DBusParseError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Use to convert GVariant into json
|
||||
RE_GVARIANT_TYPE = re.compile(
|
||||
r"(?:boolean|byte|int16|uint16|int32|uint32|handle|int64|uint64|double|"
|
||||
r"string|objectpath|signature) ")
|
||||
RE_GVARIANT_TULPE = re.compile(r"^\((.*),\)$")
|
||||
RE_GVARIANT_VARIANT = re.compile(
|
||||
r"(?<=(?: |{|\[))<((?:'|\").*?(?:'|\")|\d+(?:\.\d+)?)>(?=(?:|]|}|,))")
|
||||
RE_GVARIANT_STRING = re.compile(r"(?<=(?: |{|\[))'(.*?)'(?=(?:|]|}|,))")
|
||||
|
||||
# Commands for dbus
|
||||
INTROSPECT = ("gdbus introspect --system --dest {bus} "
|
||||
"--object-path {obj} --xml")
|
||||
CALL = ("gdbus call --system --dest {bus} --object-path {inf} "
|
||||
"--method {inf}.{method} {args}")
|
||||
"--object-path {object} --xml")
|
||||
CALL = ("gdbus call --system --dest {bus} --object-path {object} "
|
||||
"--method {method} {args}")
|
||||
|
||||
DBUS_METHOD_GETALL = 'org.freedesktop.DBus.Properties.GetAll'
|
||||
|
||||
|
||||
class DBus:
|
||||
@ -28,7 +35,7 @@ class DBus:
|
||||
"""Initialize dbus object."""
|
||||
self.bus_name = bus_name
|
||||
self.object_path = object_path
|
||||
self.data = {}
|
||||
self.methods = set()
|
||||
|
||||
@staticmethod
|
||||
async def connect(bus_name, object_path):
|
||||
@ -36,14 +43,14 @@ class DBus:
|
||||
self = DBus(bus_name, object_path)
|
||||
self._init_proxy() # pylint: disable=protected-access
|
||||
|
||||
_LOGGER.info("Connect to dbus: %s", bus_name)
|
||||
_LOGGER.info("Connect to dbus: %s - %s", bus_name, object_path)
|
||||
return self
|
||||
|
||||
async def _init_proxy(self):
|
||||
"""Read object data."""
|
||||
"""Read interface data."""
|
||||
command = shlex.split(INTROSPECT.format(
|
||||
bus=self.bus_name,
|
||||
obj=self.object_path
|
||||
object=self.object_path
|
||||
))
|
||||
|
||||
# Ask data
|
||||
@ -59,14 +66,15 @@ class DBus:
|
||||
|
||||
# Read available methods
|
||||
for interface in xml.findall("/node/interface"):
|
||||
methods = set()
|
||||
interface_name = interface.get('name')
|
||||
for method in interface.findall("/method"):
|
||||
methods.add(method.get('name'))
|
||||
self.data[interface.get('name')] = methods
|
||||
method_name = method.get('name')
|
||||
self.methods.add(f"{interface_name}.{method_name}")
|
||||
|
||||
@staticmethod
|
||||
def _gvariant(raw):
|
||||
"""Parse GVariant input to python."""
|
||||
raw = RE_GVARIANT_TYPE.sub("", raw)
|
||||
raw = RE_GVARIANT_TULPE.sub(r"[\1]", raw)
|
||||
raw = RE_GVARIANT_VARIANT.sub(r"\1", raw)
|
||||
raw = RE_GVARIANT_STRING.sub(r"\"\1\"", raw)
|
||||
@ -74,25 +82,32 @@ class DBus:
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except json.JSONDecodeError as err:
|
||||
_LOGGER.error("Can't parse '%s': %s", raw, err)
|
||||
_LOGGER.error("Can't parse '%s': %s", raw, err)
|
||||
raise DBusParseError() from None
|
||||
|
||||
async def call_dbus(self, interface, method, *args):
|
||||
async def call_dbus(self, method, *args):
|
||||
"""Call a dbus method."""
|
||||
command = shlex.split(CALL.format(
|
||||
bus=self.bus_name,
|
||||
inf=interface,
|
||||
object=self.object_path,
|
||||
method=method,
|
||||
args=" ".join(map(str, args))
|
||||
))
|
||||
|
||||
# Run command
|
||||
_LOGGER.info("Call %s no %s", method, interface)
|
||||
_LOGGER.info("Call %s on %s", method, self.object_path)
|
||||
data = await self._send(command)
|
||||
|
||||
# Parse and return data
|
||||
return self._gvariant(data)
|
||||
|
||||
def get_properties(self, interface):
|
||||
"""Read all properties from interface.
|
||||
|
||||
Return a coroutine.
|
||||
"""
|
||||
return self.call_dbus(DBUS_METHOD_GETALL, interface)
|
||||
|
||||
async def _send(self, command):
|
||||
"""Send command over dbus."""
|
||||
# Run command
|
||||
@ -117,13 +132,9 @@ class DBus:
|
||||
# End
|
||||
return data.decode()
|
||||
|
||||
def __getattr__(self, interface):
|
||||
def __getattr__(self, name):
|
||||
"""Mapping to dbus method."""
|
||||
interface = f"{self.object_path}.{interface}"
|
||||
if interface not in self.data:
|
||||
raise AttributeError()
|
||||
|
||||
return DBusCallWrapper(self, interface)
|
||||
return getattr(DBusCallWrapper(self, self.object_path), name)
|
||||
|
||||
|
||||
class DBusCallWrapper:
|
||||
@ -134,16 +145,23 @@ class DBusCallWrapper:
|
||||
self.dbus = dbus
|
||||
self.interface = interface
|
||||
|
||||
def __call__(self):
|
||||
"""Should never be called."""
|
||||
_LOGGER.error("DBus method %s not exists!", self.interface)
|
||||
raise DBusFatalError()
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Mapping to dbus method."""
|
||||
if name not in self.dbus.data[self.interface]:
|
||||
raise AttributeError()
|
||||
interface = f"{self.interface}.{name}"
|
||||
|
||||
if interface not in self.dbus.methods:
|
||||
return DBusCallWrapper(self.dbus, interface)
|
||||
|
||||
def _method_wrapper(*args):
|
||||
"""Wrap method.
|
||||
|
||||
Return a coroutine
|
||||
"""
|
||||
return self.dbus.call_dbus(self.interface, self.name, *args)
|
||||
return self.dbus.call_dbus(self.interface, *args)
|
||||
|
||||
return _method_wrapper
|
||||
|
Loading…
x
Reference in New Issue
Block a user