Improve gdbus error handling (#1252)

* Improve gdbus error handling

* Fix logging type

* Detect no dbus

* Fix issue with complex

* Update hassio/dbus/__init__.py

Co-Authored-By: Franck Nijhof <frenck@frenck.nl>

* Update hassio/dbus/hostname.py

Co-Authored-By: Franck Nijhof <frenck@frenck.nl>

* Update hassio/dbus/rauc.py

Co-Authored-By: Franck Nijhof <frenck@frenck.nl>

* Update hassio/dbus/systemd.py

Co-Authored-By: Franck Nijhof <frenck@frenck.nl>

* Fix black
This commit is contained in:
Pascal Vizeli 2019-08-22 12:48:02 +02:00 committed by GitHub
parent e8cc85c487
commit c0e3ccdb83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 467 additions and 136 deletions

View File

@ -6,7 +6,7 @@ import sys
from hassio import bootstrap from hassio import bootstrap
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
def initialize_event_loop(): def initialize_event_loop():

View File

@ -19,7 +19,7 @@ from ..store.addon import AddonStore
from .addon import Addon from .addon import Addon
from .data import AddonsData from .data import AddonsData
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
AnyAddon = Union[Addon, AddonStore] AnyAddon = Union[Addon, AddonStore]

View File

@ -55,7 +55,7 @@ from .model import AddonModel, Data
from .utils import remove_data from .utils import remove_data
from .validate import SCHEMA_ADDON_SNAPSHOT, validate_options from .validate import SCHEMA_ADDON_SNAPSHOT, validate_options
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
RE_WEBUI = re.compile( RE_WEBUI = re.compile(
r"^(?:(?P<s_prefix>https?)|\[PROTO:(?P<t_proto>\w+)\])" r"^(?:(?P<s_prefix>https?)|\[PROTO:(?P<t_proto>\w+)\])"

View File

@ -17,7 +17,7 @@ from ..store.addon import AddonStore
from .addon import Addon from .addon import Addon
from .validate import SCHEMA_ADDONS_FILE from .validate import SCHEMA_ADDONS_FILE
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
Config = Dict[str, Any] Config = Dict[str, Any]

View File

@ -22,7 +22,7 @@ from ..const import (
if TYPE_CHECKING: if TYPE_CHECKING:
from .model import AddonModel from .model import AddonModel
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
def rating_security(addon: AddonModel) -> int: def rating_security(addon: AddonModel) -> int:

View File

@ -95,7 +95,7 @@ from ..validate import (
UUID_MATCH, UUID_MATCH,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
RE_VOLUME = re.compile(r"^(config|ssl|addons|backup|share)(?::(rw|ro))?$") RE_VOLUME = re.compile(r"^(config|ssl|addons|backup|share)(?::(rw|ro))?$")

View File

@ -22,7 +22,7 @@ from .services import APIServices
from .snapshots import APISnapshots from .snapshots import APISnapshots
from .supervisor import APISupervisor from .supervisor import APISupervisor
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class RestAPI(CoreSysAttributes): class RestAPI(CoreSysAttributes):

View File

@ -94,7 +94,7 @@ from ..exceptions import APIError
from ..validate import ALSA_DEVICE, DOCKER_PORTS from ..validate import ALSA_DEVICE, DOCKER_PORTS
from .utils import api_process, api_process_raw, api_validate from .utils import api_process, api_process_raw, api_validate
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)}) SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})

View File

@ -10,7 +10,7 @@ from ..const import REQUEST_FROM, CONTENT_TYPE_JSON, CONTENT_TYPE_URL
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import APIForbidden from ..exceptions import APIForbidden
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class APIAuth(CoreSysAttributes): class APIAuth(CoreSysAttributes):

View File

@ -26,7 +26,7 @@ from ..exceptions import APIError
from ..validate import DNS_SERVER_LIST from ..validate import DNS_SERVER_LIST
from .utils import api_process, api_process_raw, api_validate from .utils import api_process, api_process_raw, api_validate
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_SERVERS): DNS_SERVER_LIST}) SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_SERVERS): DNS_SERVER_LIST})

View File

@ -12,7 +12,7 @@ from ..const import (
) )
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class APIHardware(CoreSysAttributes): class APIHardware(CoreSysAttributes):

View File

@ -16,7 +16,7 @@ from ..const import (
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from .utils import api_process, api_validate from .utils import api_process, api_validate
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)}) SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})

View File

@ -36,7 +36,7 @@ from ..exceptions import APIError
from ..validate import DOCKER_IMAGE, NETWORK_PORT from ..validate import DOCKER_IMAGE, NETWORK_PORT
from .utils import api_process, api_process_raw, api_validate from .utils import api_process, api_process_raw, api_validate
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
SCHEMA_OPTIONS = vol.Schema( SCHEMA_OPTIONS = vol.Schema(

View File

@ -20,7 +20,7 @@ from ..const import (
) )
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
SERVICE = "service" SERVICE = "service"

View File

@ -19,7 +19,7 @@ from ..const import (
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from .utils import api_process from .utils import api_process
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class APIInfo(CoreSysAttributes): class APIInfo(CoreSysAttributes):

View File

@ -28,7 +28,7 @@ from ..const import (
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from .utils import api_process from .utils import api_process
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class APIIngress(CoreSysAttributes): class APIIngress(CoreSysAttributes):

View File

@ -14,7 +14,7 @@ from ..const import HEADER_HA_ACCESS
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import HomeAssistantAuthError, HomeAssistantAPIError, APIError from ..exceptions import HomeAssistantAuthError, HomeAssistantAPIError, APIError
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class APIProxy(CoreSysAttributes): class APIProxy(CoreSysAttributes):

View File

@ -16,7 +16,7 @@ from ..const import (
) )
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
# fmt: off # fmt: off

View File

@ -28,7 +28,7 @@ from ..const import (
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import APIError from ..exceptions import APIError
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter

View File

@ -44,7 +44,7 @@ from ..utils.validate import validate_timezone
from ..validate import CHANNELS, LOG_LEVEL, REPOSITORIES, WAIT_BOOT from ..validate import CHANNELS, LOG_LEVEL, REPOSITORIES, WAIT_BOOT
from .utils import api_process, api_process_raw, api_validate from .utils import api_process, api_process_raw, api_validate
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
SCHEMA_OPTIONS = vol.Schema( SCHEMA_OPTIONS = vol.Schema(

View File

@ -16,7 +16,7 @@ from ..const import (
) )
from ..exceptions import HassioError, APIError, APIForbidden from ..exceptions import HassioError, APIError, APIForbidden
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
def json_loads(data): def json_loads(data):

View File

@ -8,7 +8,7 @@ from .coresys import CoreSys, CoreSysAttributes
from .exceptions import HassioArchNotFound, JsonFileError from .exceptions import HassioArchNotFound, JsonFileError
from .utils.json import read_json_file from .utils.json import read_json_file
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
ARCH_JSON: Path = Path(__file__).parent.joinpath("data/arch.json") ARCH_JSON: Path = Path(__file__).parent.joinpath("data/arch.json")

View File

@ -8,7 +8,7 @@ from .utils.json import JsonConfig
from .validate import SCHEMA_AUTH_CONFIG from .validate import SCHEMA_AUTH_CONFIG
from .exceptions import AuthError, HomeAssistantAPIError from .exceptions import AuthError, HomeAssistantAPIError
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class Auth(JsonConfig, CoreSysAttributes): class Auth(JsonConfig, CoreSysAttributes):

View File

@ -29,7 +29,7 @@ from .tasks import Tasks
from .updater import Updater from .updater import Updater
from .utils.dt import fetch_timezone from .utils.dt import fetch_timezone
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
ENV_SHARE = "SUPERVISOR_SHARE" ENV_SHARE = "SUPERVISOR_SHARE"
ENV_NAME = "SUPERVISOR_NAME" ENV_NAME = "SUPERVISOR_NAME"

View File

@ -19,7 +19,7 @@ from .utils.dt import parse_datetime
from .utils.json import JsonConfig from .utils.json import JsonConfig
from .validate import SCHEMA_HASSIO_CONFIG from .validate import SCHEMA_HASSIO_CONFIG
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
HOMEASSISTANT_CONFIG = PurePath("homeassistant") HOMEASSISTANT_CONFIG = PurePath("homeassistant")

View File

@ -14,7 +14,7 @@ from .const import (
) )
from .exceptions import HassioError, HomeAssistantError, SupervisorUpdateError from .exceptions import HassioError, HomeAssistantError, SupervisorUpdateError
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class HassIO(CoreSysAttributes): class HassIO(CoreSysAttributes):

View File

@ -1,39 +1,49 @@
"""D-Bus interface objects.""" """D-Bus interface objects."""
import logging
from .systemd import Systemd from .systemd import Systemd
from .hostname import Hostname from .hostname import Hostname
from .rauc import Rauc from .rauc import Rauc
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes, CoreSys
from ..exceptions import DBusNotConnectedError
_LOGGER: logging.Logger = logging.getLogger(__name__)
class DBusManager(CoreSysAttributes): class DBusManager(CoreSysAttributes):
"""A DBus Interface handler.""" """A DBus Interface handler."""
def __init__(self, coresys): def __init__(self, coresys: CoreSys) -> None:
"""Initialize D-Bus interface.""" """Initialize D-Bus interface."""
self.coresys = coresys self.coresys: CoreSys = coresys
self._systemd = Systemd() self._systemd: Systemd = Systemd()
self._hostname = Hostname() self._hostname: Hostname = Hostname()
self._rauc = Rauc() self._rauc: Rauc = Rauc()
@property @property
def systemd(self): def systemd(self) -> Systemd:
"""Return the systemd interface.""" """Return the systemd interface."""
return self._systemd return self._systemd
@property @property
def hostname(self): def hostname(self) -> Hostname:
"""Return the hostname interface.""" """Return the hostname interface."""
return self._hostname return self._hostname
@property @property
def rauc(self): def rauc(self) -> Rauc:
"""Return the rauc interface.""" """Return the rauc interface."""
return self._rauc return self._rauc
async def load(self): async def load(self) -> None:
"""Connect interfaces to D-Bus.""" """Connect interfaces to D-Bus."""
await self.systemd.connect()
await self.hostname.connect() try:
await self.rauc.connect() await self.systemd.connect()
await self.hostname.connect()
await self.rauc.connect()
except DBusNotConnectedError:
_LOGGER.error(
"No DBus support from Host. Disabled any kind of host control!"
)

View File

@ -3,10 +3,10 @@ import logging
from .interface import DBusInterface from .interface import DBusInterface
from .utils import dbus_connected from .utils import dbus_connected
from ..exceptions import DBusError from ..exceptions import DBusError, DBusInterfaceError
from ..utils.gdbus import DBus from ..utils.gdbus import DBus
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
DBUS_NAME = "org.freedesktop.hostname1" DBUS_NAME = "org.freedesktop.hostname1"
DBUS_OBJECT = "/org/freedesktop/hostname1" DBUS_OBJECT = "/org/freedesktop/hostname1"
@ -21,6 +21,10 @@ class Hostname(DBusInterface):
self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT) self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT)
except DBusError: except DBusError:
_LOGGER.warning("Can't connect to hostname") _LOGGER.warning("Can't connect to hostname")
except DBusInterfaceError:
_LOGGER.warning(
"No hostname support on the host. Hostname functions have been disabled."
)
@dbus_connected @dbus_connected
def set_static_hostname(self, hostname): def set_static_hostname(self, hostname):

View File

@ -3,10 +3,10 @@ import logging
from .interface import DBusInterface from .interface import DBusInterface
from .utils import dbus_connected from .utils import dbus_connected
from ..exceptions import DBusError from ..exceptions import DBusError, DBusInterfaceError
from ..utils.gdbus import DBus from ..utils.gdbus import DBus
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
DBUS_NAME = "de.pengutronix.rauc" DBUS_NAME = "de.pengutronix.rauc"
DBUS_OBJECT = "/" DBUS_OBJECT = "/"
@ -21,6 +21,8 @@ class Rauc(DBusInterface):
self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT) self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT)
except DBusError: except DBusError:
_LOGGER.warning("Can't connect to rauc") _LOGGER.warning("Can't connect to rauc")
except DBusInterfaceError:
_LOGGER.warning("Host has no rauc support. OTA updates have been disabled.")
@dbus_connected @dbus_connected
def install(self, raucb_file): def install(self, raucb_file):

View File

@ -3,10 +3,10 @@ import logging
from .interface import DBusInterface from .interface import DBusInterface
from .utils import dbus_connected from .utils import dbus_connected
from ..exceptions import DBusError from ..exceptions import DBusError, DBusInterfaceError
from ..utils.gdbus import DBus from ..utils.gdbus import DBus
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
DBUS_NAME = "org.freedesktop.systemd1" DBUS_NAME = "org.freedesktop.systemd1"
DBUS_OBJECT = "/org/freedesktop/systemd1" DBUS_OBJECT = "/org/freedesktop/systemd1"
@ -21,6 +21,10 @@ class Systemd(DBusInterface):
self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT) self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT)
except DBusError: except DBusError:
_LOGGER.warning("Can't connect to systemd") _LOGGER.warning("Can't connect to systemd")
except DBusInterfaceError:
_LOGGER.warning(
"No systemd support on the host. Host control has been disabled."
)
@dbus_connected @dbus_connected
def reboot(self): def reboot(self):

View File

@ -19,7 +19,7 @@ from .validate import SCHEMA_DISCOVERY_CONFIG, valid_discovery_config
if TYPE_CHECKING: if TYPE_CHECKING:
from ..addons.addon import Addon from ..addons.addon import Addon
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
CMD_NEW = "post" CMD_NEW = "post"
CMD_DEL = "delete" CMD_DEL = "delete"

View File

@ -18,7 +18,7 @@ from .misc.forwarder import DNSForward
from .utils.json import JsonConfig from .utils.json import JsonConfig
from .validate import SCHEMA_DNS_CONFIG from .validate import SCHEMA_DNS_CONFIG
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
COREDNS_TMPL: Path = Path(__file__).parents[0].joinpath("data/coredns.tmpl") COREDNS_TMPL: Path = Path(__file__).parents[0].joinpath("data/coredns.tmpl")
RESOLV_CONF: Path = Path("/etc/resolv.conf") RESOLV_CONF: Path = Path("/etc/resolv.conf")

View File

@ -11,7 +11,7 @@ from ..const import SOCKET_DOCKER, DNS_SUFFIX
from ..exceptions import DockerAPIError from ..exceptions import DockerAPIError
from .network import DockerNetwork from .network import DockerNetwork
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
@attr.s(frozen=True) @attr.s(frozen=True)

View File

@ -32,7 +32,7 @@ if TYPE_CHECKING:
from ..addons.addon import Addon from ..addons.addon import Addon
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
AUDIO_DEVICE = "/dev/snd:/dev/snd:rwm" AUDIO_DEVICE = "/dev/snd:/dev/snd:rwm"
NO_ADDDRESS = ip_address("0.0.0.0") NO_ADDDRESS = ip_address("0.0.0.0")

View File

@ -7,7 +7,7 @@ from ..coresys import CoreSysAttributes
from ..exceptions import DockerAPIError from ..exceptions import DockerAPIError
from .interface import DockerInterface from .interface import DockerInterface
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
DNS_DOCKER_NAME: str = "hassio_dns" DNS_DOCKER_NAME: str = "hassio_dns"

View File

@ -6,7 +6,7 @@ import docker
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from .interface import DockerInterface from .interface import DockerInterface
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class DockerHassOSCli(DockerInterface, CoreSysAttributes): class DockerHassOSCli(DockerInterface, CoreSysAttributes):

View File

@ -10,7 +10,7 @@ from ..const import ENV_TIME, ENV_TOKEN, LABEL_MACHINE
from ..exceptions import DockerAPIError from ..exceptions import DockerAPIError
from .interface import CommandReturn, DockerInterface from .interface import CommandReturn, DockerInterface
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
HASS_DOCKER_NAME = "homeassistant" HASS_DOCKER_NAME = "homeassistant"

View File

@ -13,7 +13,7 @@ from ..exceptions import DockerAPIError
from ..utils import process_lock from ..utils import process_lock
from .stats import DockerStats from .stats import DockerStats
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class DockerInterface(CoreSysAttributes): class DockerInterface(CoreSysAttributes):

View File

@ -8,7 +8,7 @@ import docker
from ..const import DOCKER_NETWORK, DOCKER_NETWORK_MASK, DOCKER_NETWORK_RANGE from ..const import DOCKER_NETWORK, DOCKER_NETWORK_MASK, DOCKER_NETWORK_RANGE
from ..exceptions import DockerAPIError from ..exceptions import DockerAPIError
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class DockerNetwork: class DockerNetwork:

View File

@ -10,7 +10,7 @@ from ..coresys import CoreSysAttributes
from ..exceptions import DockerAPIError from ..exceptions import DockerAPIError
from .interface import DockerInterface from .interface import DockerInterface
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class DockerSupervisor(DockerInterface, CoreSysAttributes): class DockerSupervisor(DockerInterface, CoreSysAttributes):

View File

@ -149,6 +149,10 @@ class DBusNotConnectedError(HostNotSupportedError):
"""DBus is not connected and call a method.""" """DBus is not connected and call a method."""
class DBusInterfaceError(HassioNotSupportedError):
"""DBus interface not connected."""
class DBusFatalError(DBusError): class DBusFatalError(DBusError):
"""DBus call going wrong.""" """DBus call going wrong."""

View File

@ -18,7 +18,7 @@ from .exceptions import (
DockerAPIError, DockerAPIError,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class HassOS(CoreSysAttributes): class HassOS(CoreSysAttributes):

View File

@ -47,7 +47,7 @@ from .utils import check_port, convert_to_ascii, process_lock
from .utils.json import JsonConfig from .utils.json import JsonConfig
from .validate import SCHEMA_HASS_CONFIG from .validate import SCHEMA_HASS_CONFIG
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
RE_YAML_ERROR = re.compile(r"homeassistant\.util\.yaml") RE_YAML_ERROR = re.compile(r"homeassistant\.util\.yaml")

View File

@ -17,7 +17,7 @@ from ..const import (
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import HassioError from ..exceptions import HassioError
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class HostManager(CoreSysAttributes): class HostManager(CoreSysAttributes):

View File

@ -9,7 +9,7 @@ import attr
from ..const import ATTR_INPUT, ATTR_OUTPUT, ATTR_DEVICES, ATTR_NAME, CHAN_ID, CHAN_TYPE from ..const import ATTR_INPUT, ATTR_OUTPUT, ATTR_DEVICES, ATTR_NAME, CHAN_ID, CHAN_TYPE
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
# pylint: disable=invalid-name # pylint: disable=invalid-name
DefaultConfig = attr.make_class("DefaultConfig", ["input", "output"]) DefaultConfig = attr.make_class("DefaultConfig", ["input", "output"])

View File

@ -7,7 +7,7 @@ from ..coresys import CoreSysAttributes
from ..exceptions import DBusError, HostAppArmorError from ..exceptions import DBusError, HostAppArmorError
from ..utils.apparmor import validate_profile from ..utils.apparmor import validate_profile
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
SYSTEMD_SERVICES = {"hassos-apparmor.service", "hassio-apparmor.service"} SYSTEMD_SERVICES = {"hassos-apparmor.service", "hassio-apparmor.service"}

View File

@ -4,7 +4,7 @@ import logging
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import HostNotSupportedError from ..exceptions import HostNotSupportedError
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
MANAGER = "manager" MANAGER = "manager"
HOSTNAME = "hostname" HOSTNAME = "hostname"

View File

@ -4,7 +4,7 @@ import logging
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import HassioError, HostNotSupportedError from ..exceptions import HassioError, HostNotSupportedError
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class InfoCenter(CoreSysAttributes): class InfoCenter(CoreSysAttributes):

View File

@ -6,7 +6,7 @@ import attr
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import HassioError, HostNotSupportedError, HostServiceError from ..exceptions import HassioError, HostNotSupportedError, HostServiceError
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
MOD_REPLACE = "replace" MOD_REPLACE = "replace"

View File

@ -12,7 +12,7 @@ from .utils.dt import utc_from_timestamp, utcnow
from .utils.json import JsonConfig from .utils.json import JsonConfig
from .validate import SCHEMA_INGRESS_CONFIG from .validate import SCHEMA_INGRESS_CONFIG
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class Ingress(JsonConfig, CoreSysAttributes): class Ingress(JsonConfig, CoreSysAttributes):

View File

@ -7,7 +7,7 @@ from typing import Optional
import async_timeout import async_timeout
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
COMMAND = "socat UDP-RECVFROM:53,fork UDP-SENDTO:{!s}:53" COMMAND = "socat UDP-RECVFROM:53,fork UDP-SENDTO:{!s}:53"

View File

@ -9,7 +9,7 @@ import pyudev
from ..const import ATTR_DEVICES, ATTR_NAME, ATTR_TYPE, CHAN_ID, CHAN_TYPE from ..const import ATTR_DEVICES, ATTR_NAME, ATTR_TYPE, CHAN_ID, CHAN_TYPE
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
ASOUND_CARDS: Path = Path("/proc/asound/cards") ASOUND_CARDS: Path = Path("/proc/asound/cards")
RE_CARDS: re.Pattern = re.compile(r"(\d+) \[(\w*) *\]: (.*\w)") RE_CARDS: re.Pattern = re.compile(r"(\d+) \[(\w*) *\]: (.*\w)")

View File

@ -3,7 +3,7 @@ import asyncio
from datetime import date, datetime, time, timedelta from datetime import date, datetime, time, timedelta
import logging import logging
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
INTERVAL = "interval" INTERVAL = "interval"
REPEAT = "repeat" REPEAT = "repeat"

View File

@ -19,7 +19,7 @@ from ..const import (
) )
from ..interface import ServiceInterface from ..interface import ServiceInterface
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter

View File

@ -9,7 +9,7 @@ from ..const import FOLDER_HOMEASSISTANT, SNAPSHOT_FULL, SNAPSHOT_PARTIAL
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..utils.dt import utcnow from ..utils.dt import utcnow
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class SnapshotManager(CoreSysAttributes): class SnapshotManager(CoreSysAttributes):

View File

@ -45,7 +45,7 @@ from ..utils.tar import SecureTarFile
from .utils import key_to_iv, password_for_validating, password_to_key, remove_folder from .utils import key_to_iv, password_for_validating, password_to_key, remove_folder
from .validate import ALL_FOLDERS, SCHEMA_SNAPSHOT from .validate import ALL_FOLDERS, SCHEMA_SNAPSHOT
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class Snapshot(CoreSysAttributes): class Snapshot(CoreSysAttributes):

View File

@ -9,7 +9,7 @@ from .addon import AddonStore
from .data import StoreData from .data import StoreData
from .repository import Repository from .repository import Repository
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
BUILTIN_REPOSITORIES = set((REPOSITORY_CORE, REPOSITORY_LOCAL)) BUILTIN_REPOSITORIES = set((REPOSITORY_CORE, REPOSITORY_LOCAL))

View File

@ -4,7 +4,7 @@ import logging
from ..coresys import CoreSys from ..coresys import CoreSys
from ..addons.model import AddonModel, Data from ..addons.model import AddonModel, Data
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class AddonStore(AddonModel): class AddonStore(AddonModel):

View File

@ -20,7 +20,7 @@ from ..utils.json import read_json_file
from .utils import extract_hash_from_path from .utils import extract_hash_from_path
from .validate import SCHEMA_REPOSITORY_CONFIG from .validate import SCHEMA_REPOSITORY_CONFIG
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class StoreData(CoreSysAttributes): class StoreData(CoreSysAttributes):

View File

@ -12,7 +12,7 @@ from ..const import URL_HASSIO_ADDONS, ATTR_URL, ATTR_BRANCH
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..validate import RE_REPOSITORY from ..validate import RE_REPOSITORY
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class GitRepo(CoreSysAttributes): class GitRepo(CoreSysAttributes):

View File

@ -5,7 +5,7 @@ from pathlib import Path
import re import re
RE_SHA1 = re.compile(r"[a-f0-9]{8}") RE_SHA1 = re.compile(r"[a-f0-9]{8}")
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
def get_hash_from_repository(name: str) -> str: def get_hash_from_repository(name: str) -> str:

View File

@ -20,7 +20,7 @@ from .exceptions import (
SupervisorUpdateError, SupervisorUpdateError,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class Supervisor(CoreSysAttributes): class Supervisor(CoreSysAttributes):

View File

@ -5,7 +5,7 @@ import logging
from .coresys import CoreSysAttributes from .coresys import CoreSysAttributes
from .exceptions import HomeAssistantError, CoreDNSError from .exceptions import HomeAssistantError, CoreDNSError
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
HASS_WATCHDOG_API = "HASS_WATCHDOG_API" HASS_WATCHDOG_API = "HASS_WATCHDOG_API"

View File

@ -24,7 +24,7 @@ from .utils import AsyncThrottle
from .utils.json import JsonConfig from .utils.json import JsonConfig
from .validate import SCHEMA_UPDATER_CONFIG from .validate import SCHEMA_UPDATER_CONFIG
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
class Updater(JsonConfig, CoreSysAttributes): class Updater(JsonConfig, CoreSysAttributes):

View File

@ -5,7 +5,7 @@ import logging
import re import re
import socket import socket
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))") RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))")

View File

@ -4,7 +4,7 @@ import re
from ..exceptions import AppArmorFileError, AppArmorInvalidError from ..exceptions import AppArmorFileError, AppArmorInvalidError
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
RE_PROFILE = re.compile(r"^profile ([^ ]+).*$") RE_PROFILE = re.compile(r"^profile ([^ ]+).*$")

View File

@ -12,7 +12,7 @@ UTC = pytz.utc
GEOIP_URL = "http://ip-api.com/json/" GEOIP_URL = "http://ip-api.com/json/"
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
# Copyright (c) Django Software Foundation and individual contributors. # Copyright (c) Django Software Foundation and individual contributors.

View File

@ -1,68 +1,87 @@
"""DBus implementation with glib.""" """DBus implementation with glib."""
from __future__ import annotations
import asyncio import asyncio
import logging import logging
import json import json
import shlex import shlex
import re import re
from signal import SIGINT from signal import SIGINT
from typing import Any, Dict, List, Optional, Set
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from ..exceptions import DBusFatalError, DBusParseError from ..exceptions import (
DBusFatalError,
DBusParseError,
DBusInterfaceError,
DBusNotConnectedError,
)
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
# Use to convert GVariant into json # Use to convert GVariant into json
RE_GVARIANT_TYPE = re.compile( RE_GVARIANT_TYPE: re.Match = re.compile(
r"(?:boolean|byte|int16|uint16|int32|uint32|handle|int64|uint64|double|" r"(?:boolean|byte|int16|uint16|int32|uint32|handle|int64|uint64|double|"
r"string|objectpath|signature) " r"string|objectpath|signature) "
) )
RE_GVARIANT_VARIANT = re.compile( RE_GVARIANT_VARIANT: re.Match = re.compile(
r"(?<=(?: |{|\[))<((?:'|\").*?(?:'|\")|\d+(?:\.\d+)?)>(?=(?:|]|}|,))" r"(?<=(?: |{|\[))<((?:'|\").*?(?:'|\")|\d+(?:\.\d+)?)>(?=(?:|]|}|,))"
) )
RE_GVARIANT_STRING = re.compile(r"(?<=(?: |{|\[|\())'(.*?)'(?=(?:|]|}|,|\)))") RE_GVARIANT_STRING_ESC: re.Match = re.compile(
RE_GVARIANT_TUPLE_O = re.compile(r"\"[^\"]*?\"|(\()") r"(?<=(?: |{|\[|\())'[^']*?\"[^']*?'(?=(?:|]|}|,|\)))"
RE_GVARIANT_TUPLE_C = re.compile(r"\"[^\"]*?\"|(,?\))") )
RE_GVARIANT_STRING: re.Match = re.compile(r"(?<=(?: |{|\[|\())'(.*?)'(?=(?:|]|}|,|\)))")
RE_GVARIANT_TUPLE_O: re.Match = re.compile(r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(\()")
RE_GVARIANT_TUPLE_C: re.Match = re.compile(r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(,?\))")
RE_MONITOR_OUTPUT = re.compile(r".+?: (?P<signal>[^ ].+) (?P<data>.*)") RE_MONITOR_OUTPUT: re.Match = re.compile(r".+?: (?P<signal>[^ ].+) (?P<data>.*)")
# Map GDBus to errors
MAP_GDBUS_ERROR: Dict[str, Any] = {
"GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown": DBusInterfaceError,
"No such file or directory": DBusNotConnectedError,
}
# Commands for dbus # Commands for dbus
INTROSPECT = "gdbus introspect --system --dest {bus} " "--object-path {object} --xml" INTROSPECT: str = "gdbus introspect --system --dest {bus} " "--object-path {object} --xml"
CALL = ( CALL: str = (
"gdbus call --system --dest {bus} --object-path {object} " "gdbus call --system --dest {bus} --object-path {object} "
"--method {method} {args}" "--method {method} {args}"
) )
MONITOR = "gdbus monitor --system --dest {bus}" MONITOR: str = "gdbus monitor --system --dest {bus}"
DBUS_METHOD_GETALL = "org.freedesktop.DBus.Properties.GetAll" DBUS_METHOD_GETALL: str = "org.freedesktop.DBus.Properties.GetAll"
class DBus: class DBus:
"""DBus handler.""" """DBus handler."""
def __init__(self, bus_name, object_path): def __init__(self, bus_name: str, object_path: str) -> None:
"""Initialize dbus object.""" """Initialize dbus object."""
self.bus_name = bus_name self.bus_name: str = bus_name
self.object_path = object_path self.object_path: str = object_path
self.methods = set() self.methods: Set[str] = set()
self.signals = set() self.signals: Set[str] = set()
@staticmethod @staticmethod
async def connect(bus_name, object_path): async def connect(bus_name: str, object_path: str) -> DBus:
"""Read object data.""" """Read object data."""
self = DBus(bus_name, object_path) self = DBus(bus_name, object_path)
await self._init_proxy() # pylint: disable=protected-access
# pylint: disable=protected-access
await self._init_proxy()
_LOGGER.info("Connect to dbus: %s - %s", bus_name, object_path) _LOGGER.info("Connect to dbus: %s - %s", bus_name, object_path)
return self return self
async def _init_proxy(self): async def _init_proxy(self) -> None:
"""Read interface data.""" """Read interface data."""
command = shlex.split( command = shlex.split(
INTROSPECT.format(bus=self.bus_name, object=self.object_path) INTROSPECT.format(bus=self.bus_name, object=self.object_path)
) )
# Ask data # Ask data
_LOGGER.info("Introspect %s on %s", self.bus_name, self.object_path) _LOGGER.debug("Introspect %s on %s", self.bus_name, self.object_path)
data = await self._send(command) data = await self._send(command)
# Parse XML # Parse XML
@ -73,7 +92,7 @@ class DBus:
raise DBusParseError() from None raise DBusParseError() from None
# Read available methods # Read available methods
_LOGGER.debug("data: %s", data) _LOGGER.debug("Introspect XML: %s", data)
for interface in xml.findall("./interface"): for interface in xml.findall("./interface"):
interface_name = interface.get("name") interface_name = interface.get("name")
@ -88,30 +107,34 @@ class DBus:
self.signals.add(f"{interface_name}.{signal_name}") self.signals.add(f"{interface_name}.{signal_name}")
@staticmethod @staticmethod
def parse_gvariant(raw): def parse_gvariant(raw: str) -> Any:
"""Parse GVariant input to python.""" """Parse GVariant input to python."""
raw = RE_GVARIANT_TYPE.sub("", raw) json_raw: str = RE_GVARIANT_TYPE.sub("", raw)
raw = RE_GVARIANT_VARIANT.sub(r"\1", raw) json_raw = RE_GVARIANT_VARIANT.sub(r"\1", json_raw)
raw = RE_GVARIANT_STRING.sub(r'"\1"', raw) json_raw = RE_GVARIANT_STRING_ESC.sub(
raw = RE_GVARIANT_TUPLE_O.sub( lambda x: x.group(0).replace('"', '\\"'), json_raw
lambda x: x.group(0) if not x.group(1) else "[", raw
) )
raw = RE_GVARIANT_TUPLE_C.sub( json_raw = RE_GVARIANT_STRING.sub(r'"\1"', json_raw)
lambda x: x.group(0) if not x.group(1) else "]", raw json_raw = RE_GVARIANT_TUPLE_O.sub(
lambda x: x.group(0) if not x.group(1) else "[", json_raw
)
json_raw = RE_GVARIANT_TUPLE_C.sub(
lambda x: x.group(0) if not x.group(1) else "]", json_raw
) )
# No data # No data
if raw.startswith("[]"): if json_raw.startswith("[]"):
return [] return []
try: try:
return json.loads(raw) return json.loads(json_raw)
except json.JSONDecodeError as err: except json.JSONDecodeError as err:
_LOGGER.error("Can't parse '%s': %s", raw, err) _LOGGER.error("Can't parse '%s': %s", json_raw, err)
_LOGGER.debug("GVariant data: '%s'", raw)
raise DBusParseError() from None raise DBusParseError() from None
@staticmethod @staticmethod
def gvariant_args(args): def gvariant_args(args: List[Any]) -> str:
"""Convert args into gvariant.""" """Convert args into gvariant."""
gvariant = "" gvariant = ""
for arg in args: for arg in args:
@ -122,11 +145,11 @@ class DBus:
elif isinstance(arg, str): elif isinstance(arg, str):
gvariant += f' "{arg}"' gvariant += f' "{arg}"'
else: else:
gvariant += " {}".format(str(arg)) gvariant += f" {arg!s}"
return gvariant.lstrip() return gvariant.lstrip()
async def call_dbus(self, method, *args): async def call_dbus(self, method: str, *args: List[Any]) -> str:
"""Call a dbus method.""" """Call a dbus method."""
command = shlex.split( command = shlex.split(
CALL.format( CALL.format(
@ -142,10 +165,9 @@ class DBus:
data = await self._send(command) data = await self._send(command)
# Parse and return data # Parse and return data
_LOGGER.debug("Receive from %s: %s", method, data)
return self.parse_gvariant(data) return self.parse_gvariant(data)
async def get_properties(self, interface): async def get_properties(self, interface: str) -> Dict[str, Any]:
"""Read all properties from interface.""" """Read all properties from interface."""
try: try:
return (await self.call_dbus(DBUS_METHOD_GETALL, interface))[0] return (await self.call_dbus(DBUS_METHOD_GETALL, interface))[0]
@ -153,7 +175,7 @@ class DBus:
_LOGGER.error("No attributes returned for %s", interface) _LOGGER.error("No attributes returned for %s", interface)
raise DBusFatalError from None raise DBusFatalError from None
async def _send(self, command): async def _send(self, command: List[str]) -> str:
"""Send command over dbus.""" """Send command over dbus."""
# Run command # Run command
_LOGGER.debug("Send dbus command: %s", command) _LOGGER.debug("Send dbus command: %s", command)
@ -171,12 +193,19 @@ class DBus:
raise DBusFatalError() from None raise DBusFatalError() from None
# Success? # Success?
if proc.returncode != 0: if proc.returncode == 0:
_LOGGER.error("DBus return error: %s", error) return data.decode()
raise DBusFatalError()
# End # Filter error
return data.decode() error = error.decode()
for msg, exception in MAP_GDBUS_ERROR.items():
if msg not in error:
continue
raise exception()
# General
_LOGGER.error("DBus return error: %s", error)
raise DBusFatalError()
def attach_signals(self, filters=None): def attach_signals(self, filters=None):
"""Generate a signals wrapper.""" """Generate a signals wrapper."""
@ -189,7 +218,7 @@ class DBus:
async for signal in signals: async for signal in signals:
return signal return signal
def __getattr__(self, name): def __getattr__(self, name: str) -> DBusCallWrapper:
"""Mapping to dbus method.""" """Mapping to dbus method."""
return getattr(DBusCallWrapper(self, self.bus_name), name) return getattr(DBusCallWrapper(self, self.bus_name), name)
@ -197,17 +226,17 @@ class DBus:
class DBusCallWrapper: class DBusCallWrapper:
"""Wrapper a DBus interface for a call.""" """Wrapper a DBus interface for a call."""
def __init__(self, dbus, interface): def __init__(self, dbus: DBus, interface: str) -> None:
"""Initialize wrapper.""" """Initialize wrapper."""
self.dbus = dbus self.dbus: DBus = dbus
self.interface = interface self.interface: str = interface
def __call__(self): def __call__(self) -> None:
"""Should never be called.""" """Should never be called."""
_LOGGER.error("DBus method %s not exists!", self.interface) _LOGGER.error("DBus method %s not exists!", self.interface)
raise DBusFatalError() raise DBusFatalError()
def __getattr__(self, name): def __getattr__(self, name: str):
"""Mapping to dbus method.""" """Mapping to dbus method."""
interface = f"{self.interface}.{name}" interface = f"{self.interface}.{name}"
@ -227,11 +256,11 @@ class DBusCallWrapper:
class DBusSignalWrapper: class DBusSignalWrapper:
"""Process Signals.""" """Process Signals."""
def __init__(self, dbus, signals=None): def __init__(self, dbus: DBus, signals: Optional[str] = None):
"""Initialize dbus signal wrapper.""" """Initialize dbus signal wrapper."""
self.dbus = dbus self.dbus: DBus = dbus
self._signals = signals self._signals: Optional[str] = signals
self._proc = None self._proc: Optional[asyncio.Process] = None
async def __aenter__(self): async def __aenter__(self):
"""Start monitor events.""" """Start monitor events."""

View File

@ -9,7 +9,7 @@ from voluptuous.humanize import humanize_error
from ..exceptions import JsonFileError from ..exceptions import JsonFileError
_LOGGER = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
def write_json_file(jsonfile: Path, data: Any) -> None: def write_json_file(jsonfile: Path, data: Any) -> None:

View File

@ -0,0 +1,278 @@
"""Test gdbus gvariant parser."""
from hassio.utils.gdbus import DBus
def test_simple_return():
"""Test Simple return value."""
raw = "(objectpath '/org/freedesktop/systemd1/job/35383',)"
# parse data
data = DBus.parse_gvariant(raw)
assert data == ["/org/freedesktop/systemd1/job/35383"]
def test_get_property():
"""Test Property parsing."""
raw = "({'Hostname': <'hassio'>, 'StaticHostname': <'hassio'>, 'PrettyHostname': <''>, 'IconName': <'computer-embedded'>, 'Chassis': <'embedded'>, 'Deployment': <'production'>, 'Location': <''>, 'KernelName': <'Linux'>, 'KernelRelease': <'4.14.98-v7'>, 'KernelVersion': <'#1 SMP Sat May 11 02:17:06 UTC 2019'>, 'OperatingSystemPrettyName': <'HassOS 2.12'>, 'OperatingSystemCPEName': <'cpe:2.3:o:home_assistant:hassos:2.12:*:production:*:*:*:rpi3:*'>, 'HomeURL': <'https://hass.io/'>},)"
# parse data
data = DBus.parse_gvariant(raw)
assert data[0] == {
"Hostname": "hassio",
"StaticHostname": "hassio",
"PrettyHostname": "",
"IconName": "computer-embedded",
"Chassis": "embedded",
"Deployment": "production",
"Location": "",
"KernelName": "Linux",
"KernelRelease": "4.14.98-v7",
"KernelVersion": "#1 SMP Sat May 11 02:17:06 UTC 2019",
"OperatingSystemPrettyName": "HassOS 2.12",
"OperatingSystemCPEName": "cpe:2.3:o:home_assistant:hassos:2.12:*:production:*:*:*:rpi3:*",
"HomeURL": "https://hass.io/",
}
def test_systemd_unitlist_simple():
"""Test Systemd Unit list simple."""
raw = "([('systemd-remount-fs.service', 'Remount Root and Kernel File Systems', 'loaded', 'active', 'exited', '', objectpath '/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice', uint32 0, '', objectpath '/'), ('sys-subsystem-net-devices-veth5714b4e.device', '/sys/subsystem/net/devices/veth5714b4e', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dveth5714b4e_2edevice', 0, '', '/'), ('rauc.service', 'Rauc Update Service', 'loaded', 'active', 'running', '', '/org/freedesktop/systemd1/unit/rauc_2eservice', 0, '', '/'), ('mnt-data-docker-overlay2-7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2-merged.mount', '/mnt/data/docker/overlay2/7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2/merged', 'loaded', 'active', 'mounted', '', '/org/freedesktop/systemd1/unit/mnt_2ddata_2ddocker_2doverlay2_2d7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2_2dmerged_2emount', 0, '', '/'), ('hassos-hardware.target', 'HassOS hardware targets', 'loaded', 'active', 'active', '', '/org/freedesktop/systemd1/unit/hassos_2dhardware_2etarget', 0, '', '/'), ('dev-zram1.device', '/dev/zram1', 'loaded', 'active', 'plugged', 'sys-devices-virtual-block-zram1.device', '/org/freedesktop/systemd1/unit/dev_2dzram1_2edevice', 0, '', '/'), ('sys-subsystem-net-devices-hassio.device', '/sys/subsystem/net/devices/hassio', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dhassio_2edevice', 0, '', '/'), ('cryptsetup.target', 'cryptsetup.target', 'not-found', 'inactive', 'dead', '', '/org/freedesktop/systemd1/unit/cryptsetup_2etarget', 0, '', '/'), ('sys-devices-virtual-net-vethd256dfa.device', '/sys/devices/virtual/net/vethd256dfa', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dnet_2dvethd256dfa_2edevice', 0, '', '/'), ('network-pre.target', 'Network (Pre)', 'loaded', 'inactive', 'dead', '', '/org/freedesktop/systemd1/unit/network_2dpre_2etarget', 0, '', '/'), ('sys-devices-virtual-net-veth5714b4e.device', '/sys/devices/virtual/net/veth5714b4e', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dnet_2dveth5714b4e_2edevice', 0, '', '/'), ('sys-kernel-debug.mount', 'Kernel Debug File System', 'loaded', 'active', 'mounted', '', '/org/freedesktop/systemd1/unit/sys_2dkernel_2ddebug_2emount', 0, '', '/'), ('slices.target', 'Slices', 'loaded', 'active', 'active', '', '/org/freedesktop/systemd1/unit/slices_2etarget', 0, '', '/'), ('etc-NetworkManager-system\x2dconnections.mount', 'NetworkManager persistent system connections', 'loaded', 'active', 'mounted', '', '/org/freedesktop/systemd1/unit/etc_2dNetworkManager_2dsystem_5cx2dconnections_2emount', 0, '', '/'), ('run-docker-netns-26ede3178729.mount', '/run/docker/netns/26ede3178729', 'loaded', 'active', 'mounted', '', '/org/freedesktop/systemd1/unit/run_2ddocker_2dnetns_2d26ede3178729_2emount', 0, '', '/'), ('dev-disk-by\x2dpath-platform\x2d3f202000.mmc\x2dpart2.device', '/dev/disk/by-path/platform-3f202000.mmc-part2', 'loaded', 'active', 'plugged', 'sys-devices-platform-soc-3f202000.mmc-mmc_host-mmc0-mmc0:e624-block-mmcblk0-mmcblk0p2.device', '/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dplatform_5cx2d3f202000_2emmc_5cx2dpart2_2edevice', 0, '', '/')],)"
# parse data
data = DBus.parse_gvariant(raw)
assert data == [
[
[
"systemd-remount-fs.service",
"Remount Root and Kernel File Systems",
"loaded",
"active",
"exited",
"",
"/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice",
0,
"",
"/",
],
[
"sys-subsystem-net-devices-veth5714b4e.device",
"/sys/subsystem/net/devices/veth5714b4e",
"loaded",
"active",
"plugged",
"",
"/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dveth5714b4e_2edevice",
0,
"",
"/",
],
[
"rauc.service",
"Rauc Update Service",
"loaded",
"active",
"running",
"",
"/org/freedesktop/systemd1/unit/rauc_2eservice",
0,
"",
"/",
],
[
"mnt-data-docker-overlay2-7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2-merged.mount",
"/mnt/data/docker/overlay2/7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2/merged",
"loaded",
"active",
"mounted",
"",
"/org/freedesktop/systemd1/unit/mnt_2ddata_2ddocker_2doverlay2_2d7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2_2dmerged_2emount",
0,
"",
"/",
],
[
"hassos-hardware.target",
"HassOS hardware targets",
"loaded",
"active",
"active",
"",
"/org/freedesktop/systemd1/unit/hassos_2dhardware_2etarget",
0,
"",
"/",
],
[
"dev-zram1.device",
"/dev/zram1",
"loaded",
"active",
"plugged",
"sys-devices-virtual-block-zram1.device",
"/org/freedesktop/systemd1/unit/dev_2dzram1_2edevice",
0,
"",
"/",
],
[
"sys-subsystem-net-devices-hassio.device",
"/sys/subsystem/net/devices/hassio",
"loaded",
"active",
"plugged",
"",
"/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dhassio_2edevice",
0,
"",
"/",
],
[
"cryptsetup.target",
"cryptsetup.target",
"not-found",
"inactive",
"dead",
"",
"/org/freedesktop/systemd1/unit/cryptsetup_2etarget",
0,
"",
"/",
],
[
"sys-devices-virtual-net-vethd256dfa.device",
"/sys/devices/virtual/net/vethd256dfa",
"loaded",
"active",
"plugged",
"",
"/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dnet_2dvethd256dfa_2edevice",
0,
"",
"/",
],
[
"network-pre.target",
"Network (Pre)",
"loaded",
"inactive",
"dead",
"",
"/org/freedesktop/systemd1/unit/network_2dpre_2etarget",
0,
"",
"/",
],
[
"sys-devices-virtual-net-veth5714b4e.device",
"/sys/devices/virtual/net/veth5714b4e",
"loaded",
"active",
"plugged",
"",
"/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dnet_2dveth5714b4e_2edevice",
0,
"",
"/",
],
[
"sys-kernel-debug.mount",
"Kernel Debug File System",
"loaded",
"active",
"mounted",
"",
"/org/freedesktop/systemd1/unit/sys_2dkernel_2ddebug_2emount",
0,
"",
"/",
],
[
"slices.target",
"Slices",
"loaded",
"active",
"active",
"",
"/org/freedesktop/systemd1/unit/slices_2etarget",
0,
"",
"/",
],
[
"etc-NetworkManager-system-connections.mount",
"NetworkManager persistent system connections",
"loaded",
"active",
"mounted",
"",
"/org/freedesktop/systemd1/unit/etc_2dNetworkManager_2dsystem_5cx2dconnections_2emount",
0,
"",
"/",
],
[
"run-docker-netns-26ede3178729.mount",
"/run/docker/netns/26ede3178729",
"loaded",
"active",
"mounted",
"",
"/org/freedesktop/systemd1/unit/run_2ddocker_2dnetns_2d26ede3178729_2emount",
0,
"",
"/",
],
[
"dev-disk-by-path-platform-3f202000.mmc-part2.device",
"/dev/disk/by-path/platform-3f202000.mmc-part2",
"loaded",
"active",
"plugged",
"sys-devices-platform-soc-3f202000.mmc-mmc_host-mmc0-mmc0:e624-block-mmcblk0-mmcblk0p2.device",
"/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dplatform_5cx2d3f202000_2emmc_5cx2dpart2_2edevice",
0,
"",
"/",
],
]
]
def test_systemd_unitlist_complex():
"""Test Systemd Unit list simple."""
raw = "([('systemd-remount-fs.service', 'Remount Root and \"Kernel File Systems\"', 'loaded', 'active', 'exited', '', objectpath '/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice', uint32 0, '', objectpath '/'), ('sys-subsystem-net-devices-veth5714b4e.device', '/sys/subsystem/net/devices/veth5714b4e for \" is', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dveth5714b4e_2edevice', 0, '', '/')],)"
# parse data
data = DBus.parse_gvariant(raw)
assert data == [
[
[
"systemd-remount-fs.service",
'Remount Root and "Kernel File Systems"',
"loaded",
"active",
"exited",
"",
"/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice",
0,
"",
"/",
],
[
"sys-subsystem-net-devices-veth5714b4e.device",
'/sys/subsystem/net/devices/veth5714b4e for " is',
"loaded",
"active",
"plugged",
"",
"/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dveth5714b4e_2edevice",
0,
"",
"/",
],
]
]