diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 413391d01..4ad374b5d 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -48,6 +48,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ jq \ dbus \ network-manager \ + apparmor-utils \ libpulse0 \ && bash <(curl https://getvcn.codenotary.com -L) \ && rm -rf /var/lib/apt/lists/* diff --git a/scripts/supervisor.sh b/scripts/supervisor.sh index 82b5c8674..21b0b4baa 100755 --- a/scripts/supervisor.sh +++ b/scripts/supervisor.sh @@ -88,6 +88,21 @@ function init_udev() { udevadm trigger && udevadm settle } +function init_os-agent() { + if pgrep os-agent; then + echo "os-agent is running" + return 0 + fi + + if [ ! -f /usr/sbin/os-agent ]; then + curl -Lo /usr/sbin/os-agent https://github.com/home-assistant/os-agent/releases/latest/download/os-agent-debian-amd64.bin + curl -Lo /etc/dbus-1/system.d/io.hass.conf https://raw.githubusercontent.com/home-assistant/os-agent/main/contrib/io.hass.conf + chmod a+x /usr/sbin/os-agent + fi + + /usr/sbin/os-agent & +} + echo "Run Supervisor" start_docker @@ -99,6 +114,7 @@ if [ "$( docker container inspect -f '{{.State.Status}}' hassio_supervisor )" == docker rm -f hassio_supervisor init_dbus init_udev + init_os-agent cleanup_lastboot run_supervisor stop_docker @@ -111,6 +127,7 @@ else cleanup_docker init_dbus init_udev + init_os-agent run_supervisor stop_docker -fi \ No newline at end of file +fi diff --git a/supervisor/bootstrap.py b/supervisor/bootstrap.py index 204905bf4..582c5556a 100644 --- a/supervisor/bootstrap.py +++ b/supervisor/bootstrap.py @@ -33,7 +33,7 @@ from .const import ( ) from .core import Core from .coresys import CoreSys -from .dbus import DBusManager +from .dbus.manager import DBusManager from .discovery import Discovery from .hardware.module import HardwareManager from .hassos import HassOS diff --git a/supervisor/coresys.py b/supervisor/coresys.py index 898909935..fabc49e43 100644 --- a/supervisor/coresys.py +++ b/supervisor/coresys.py @@ -21,7 +21,7 @@ if TYPE_CHECKING: from .arch import CpuArch from .auth import Auth from .core import Core - from .dbus import DBusManager + from .dbus.manager import DBusManager from .discovery import Discovery from .hardware.module import HardwareManager from .hassos import HassOS diff --git a/supervisor/dbus/__init__.py b/supervisor/dbus/__init__.py index 81780ba48..ac5e8768f 100644 --- a/supervisor/dbus/__init__.py +++ b/supervisor/dbus/__init__.py @@ -1,84 +1 @@ -"""D-Bus interface objects.""" -import logging -from typing import List - -from ..const import SOCKET_DBUS -from ..coresys import CoreSys, CoreSysAttributes -from .hostname import Hostname -from .interface import DBusInterface -from .logind import Logind -from .network import NetworkManager -from .rauc import Rauc -from .systemd import Systemd -from .timedate import TimeDate - -_LOGGER: logging.Logger = logging.getLogger(__name__) - - -class DBusManager(CoreSysAttributes): - """A DBus Interface handler.""" - - def __init__(self, coresys: CoreSys) -> None: - """Initialize D-Bus interface.""" - self.coresys: CoreSys = coresys - - self._systemd: Systemd = Systemd() - self._logind: Logind = Logind() - self._hostname: Hostname = Hostname() - self._rauc: Rauc = Rauc() - self._network: NetworkManager = NetworkManager() - self._timedate: TimeDate = TimeDate() - - @property - def systemd(self) -> Systemd: - """Return the systemd interface.""" - return self._systemd - - @property - def logind(self) -> Logind: - """Return the logind interface.""" - return self._logind - - @property - def timedate(self) -> TimeDate: - """Return the timedate interface.""" - return self._timedate - - @property - def hostname(self) -> Hostname: - """Return the hostname interface.""" - return self._hostname - - @property - def rauc(self) -> Rauc: - """Return the rauc interface.""" - return self._rauc - - @property - def network(self) -> NetworkManager: - """Return NetworkManager interface.""" - return self._network - - async def load(self) -> None: - """Connect interfaces to D-Bus.""" - if not SOCKET_DBUS.exists(): - _LOGGER.error( - "No D-Bus support on Host. Disabled any kind of host control!" - ) - return - - dbus_loads: List[DBusInterface] = [ - self.systemd, - self.logind, - self.hostname, - self.timedate, - self.network, - self.rauc, - ] - for dbus in dbus_loads: - try: - await dbus.connect() - except Exception as err: # pylint: disable=broad-except - _LOGGER.warning("Can't load dbus interface %s: %s", dbus.name, err) - - self.sys_host.supported_features.cache_clear() +"""D-Bus interfaces.""" diff --git a/supervisor/dbus/agent/__init__.py b/supervisor/dbus/agent/__init__.py new file mode 100644 index 000000000..de567f2b8 --- /dev/null +++ b/supervisor/dbus/agent/__init__.py @@ -0,0 +1,99 @@ +"""OS-Agent implementation for DBUS.""" +import asyncio +import logging +from typing import Any, Dict + +from awesomeversion import AwesomeVersion + +from ...exceptions import DBusError, DBusInterfaceError +from ...utils.gdbus import DBus +from ..const import ( + DBUS_ATTR_DIAGNOSTICS, + DBUS_ATTR_VERSION, + DBUS_NAME_HAOS, + DBUS_OBJECT_HAOS, +) +from ..interface import DBusInterface, dbus_property +from ..utils import dbus_connected +from .apparmor import AppArmor +from .cgroup import CGroup +from .datadisk import DataDisk +from .system import System + +_LOGGER: logging.Logger = logging.getLogger(__name__) + + +class OSAgent(DBusInterface): + """Handle D-Bus interface for OS-Agent.""" + + name = DBUS_NAME_HAOS + + def __init__(self) -> None: + """Initialize Properties.""" + self.properties: Dict[str, Any] = {} + + self._cgroup: CGroup = CGroup() + self._apparmor: AppArmor = AppArmor() + self._system: System = System() + self._datadisk: DataDisk = DataDisk() + + @property + def cgroup(self) -> CGroup: + """Return CGroup DBUS object.""" + return self._cgroup + + @property + def apparmor(self) -> AppArmor: + """Return AppArmor DBUS object.""" + return self._apparmor + + @property + def system(self) -> System: + """Return System DBUS object.""" + return self._system + + @property + def datadisk(self) -> DataDisk: + """Return DataDisk DBUS object.""" + return self._datadisk + + @property + @dbus_property + def version(self) -> AwesomeVersion: + """Return version of OS-Agent.""" + return AwesomeVersion(self.properties[DBUS_ATTR_VERSION]) + + @property + @dbus_property + def diagnostics(self) -> bool: + """Return if diagnostics is enabled on OS-Agent.""" + return self.properties[DBUS_ATTR_DIAGNOSTICS] + + @diagnostics.setter + def diagnostics(self, value: bool) -> None: + """Enable or disable OS-Agent diagnostics.""" + asyncio.create_task( + self.dbus.set_property(DBUS_NAME_HAOS, DBUS_ATTR_DIAGNOSTICS, value) + ) + + async def connect(self) -> None: + """Connect to system's D-Bus.""" + try: + self.dbus = await DBus.connect(DBUS_NAME_HAOS, DBUS_OBJECT_HAOS) + await self.cgroup.connect() + await self.apparmor.connect() + await self.system.connect() + await self.datadisk.connect() + except DBusError: + _LOGGER.warning("Can't connect to OS-Agent") + except DBusInterfaceError: + _LOGGER.warning( + "No OS-Agent support on the host. Some Host functions have been disabled." + ) + + @dbus_connected + async def update(self): + """Update Properties.""" + self.properties = await self.dbus.get_properties(DBUS_NAME_HAOS) + await self.apparmor.update() + await self.datadisk.update() diff --git a/supervisor/dbus/agent/apparmor.py b/supervisor/dbus/agent/apparmor.py new file mode 100644 index 000000000..17433d29c --- /dev/null +++ b/supervisor/dbus/agent/apparmor.py @@ -0,0 +1,48 @@ +"""AppArmor object for OS-Agent.""" +from pathlib import Path +from typing import Any, Dict + +from awesomeversion import AwesomeVersion + +from ...utils.gdbus import DBus +from ..const import ( + DBUS_ATTR_PARSER_VERSION, + DBUS_NAME_HAOS, + DBUS_NAME_HAOS_APPARMOR, + DBUS_OBJECT_HAOS_APPARMOR, +) +from ..interface import DBusInterface, dbus_property +from ..utils import dbus_connected + + +class AppArmor(DBusInterface): + """AppArmor object for OS Agent.""" + + def __init__(self) -> None: + """Initialize Properties.""" + self.properties: Dict[str, Any] = {} + + @property + @dbus_property + def version(self) -> AwesomeVersion: + """Return version of host AppArmor parser.""" + return AwesomeVersion(self.properties[DBUS_ATTR_PARSER_VERSION]) + + async def connect(self) -> None: + """Get connection information.""" + self.dbus = await DBus.connect(DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_APPARMOR) + + @dbus_connected + async def update(self): + """Update Properties.""" + self.properties = await self.dbus.get_properties(DBUS_NAME_HAOS_APPARMOR) + + @dbus_connected + async def load_profile(self, profile: Path, cache: Path) -> None: + """Load/Update AppArmor profile.""" + await self.dbus.AppArmor.LoadProfile(profile.as_posix(), cache.as_posix()) + + @dbus_connected + async def unload_profile(self, profile: Path, cache: Path) -> None: + """Remove AppArmor profile.""" + await self.dbus.AppArmor.UnloadProfile(profile.as_posix(), cache.as_posix()) diff --git a/supervisor/dbus/agent/cgroup.py b/supervisor/dbus/agent/cgroup.py new file mode 100644 index 000000000..e51f225ad --- /dev/null +++ b/supervisor/dbus/agent/cgroup.py @@ -0,0 +1,19 @@ +"""CGroup object for OS-Agent.""" + +from ...utils.gdbus import DBus +from ..const import DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_CGROUP +from ..interface import DBusInterface +from ..utils import dbus_connected + + +class CGroup(DBusInterface): + """CGroup object for OS Agent.""" + + async def connect(self) -> None: + """Get connection information.""" + self.dbus = await DBus.connect(DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_CGROUP) + + @dbus_connected + async def add_devices_allowed(self, container_id: str, permission: str) -> None: + """Update cgroup devices and add new devices.""" + await self.dbus.CGroup.AddDevicesAllowed(container_id, permission) diff --git a/supervisor/dbus/agent/datadisk.py b/supervisor/dbus/agent/datadisk.py new file mode 100644 index 000000000..479a2fb89 --- /dev/null +++ b/supervisor/dbus/agent/datadisk.py @@ -0,0 +1,41 @@ +"""DataDisk object for OS-Agent.""" +from pathlib import Path +from typing import Any, Dict + +from ...utils.gdbus import DBus +from ..const import ( + DBUS_ATTR_CURRENT_DEVICE, + DBUS_NAME_HAOS, + DBUS_NAME_HAOS_DATADISK, + DBUS_OBJECT_HAOS_DATADISK, +) +from ..interface import DBusInterface, dbus_property +from ..utils import dbus_connected + + +class DataDisk(DBusInterface): + """DataDisk object for OS Agent.""" + + def __init__(self) -> None: + """Initialize Properties.""" + self.properties: Dict[str, Any] = {} + + @property + @dbus_property + def current_device(self) -> Path: + """Return current device used for data.""" + return Path(self.properties[DBUS_ATTR_CURRENT_DEVICE]) + + async def connect(self) -> None: + """Get connection information.""" + self.dbus = await DBus.connect(DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_DATADISK) + + @dbus_connected + async def update(self): + """Update Properties.""" + self.properties = await self.dbus.get_properties(DBUS_NAME_HAOS_DATADISK) + + @dbus_connected + async def change_device(self, device: Path) -> None: + """Load/Update AppArmor profile.""" + await self.dbus.DataDisk.ChangeDevice(device.as_posix()) diff --git a/supervisor/dbus/agent/system.py b/supervisor/dbus/agent/system.py new file mode 100644 index 000000000..beddd5e00 --- /dev/null +++ b/supervisor/dbus/agent/system.py @@ -0,0 +1,19 @@ +"""System object for OS-Agent.""" + +from ...utils.gdbus import DBus +from ..const import DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_SYSTEM +from ..interface import DBusInterface +from ..utils import dbus_connected + + +class System(DBusInterface): + """System object for OS Agent.""" + + async def connect(self) -> None: + """Get connection information.""" + self.dbus = await DBus.connect(DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_SYSTEM) + + @dbus_connected + async def schedule_wipe_device(self) -> None: + """Schedule a factory reset on next system boot.""" + await self.dbus.System.ScheduleWipeDevice() diff --git a/supervisor/dbus/const.py b/supervisor/dbus/const.py index 1c9e12821..1351363b9 100644 --- a/supervisor/dbus/const.py +++ b/supervisor/dbus/const.py @@ -20,6 +20,12 @@ DBUS_NAME_NM_CONNECTION_ACTIVE_CHANGED = ( DBUS_NAME_SYSTEMD = "org.freedesktop.systemd1" DBUS_NAME_LOGIND = "org.freedesktop.login1" DBUS_NAME_TIMEDATE = "org.freedesktop.timedate1" +DBUS_NAME_HAOS = "io.hass.os" +DBUS_NAME_HAOS_CGROUP = "io.hass.os.CGroup" +DBUS_NAME_HAOS_APPARMOR = "io.hass.os.AppArmor" +DBUS_NAME_HAOS_SYSTEM = "io.hass.os.System" +DBUS_NAME_HAOS_DATADISK = "io.hass.os.DataDisk" + DBUS_OBJECT_BASE = "/" DBUS_OBJECT_DNS = "/org/freedesktop/NetworkManager/DnsManager" @@ -29,6 +35,11 @@ DBUS_OBJECT_NM = "/org/freedesktop/NetworkManager" DBUS_OBJECT_SYSTEMD = "/org/freedesktop/systemd1" DBUS_OBJECT_LOGIND = "/org/freedesktop/login1" DBUS_OBJECT_TIMEDATE = "/org/freedesktop/timedate1" +DBUS_OBJECT_HAOS = "/io/hass/os" +DBUS_OBJECT_HAOS_CGROUP = "/io/hass/os/CGroup" +DBUS_OBJECT_HAOS_APPARMOR = "/io/hass/os/AppArmor" +DBUS_OBJECT_HAOS_SYSTEM = "/io/hass/os/System" +DBUS_OBJECT_HAOS_DATADISK = "/io/hass/os/DataDisk" DBUS_ATTR_ACTIVE_CONNECTIONS = "ActiveConnections" DBUS_ATTR_ACTIVE_CONNECTION = "ActiveConnection" @@ -77,6 +88,9 @@ DBUS_ATTR_LOCALRTC = "LocalRTC" DBUS_ATTR_NTP = "NTP" DBUS_ATTR_NTPSYNCHRONIZED = "NTPSynchronized" DBUS_ATTR_TIMEUSEC = "TimeUSec" +DBUS_ATTR_DIAGNOSTICS = "Diagnostics" +DBUS_ATTR_CURRENT_DEVICE = "CurrentDevice" +DBUS_ATTR_PARSER_VERSION = "ParserVersion" class RaucState(str, Enum): diff --git a/supervisor/dbus/manager.py b/supervisor/dbus/manager.py new file mode 100644 index 000000000..8ea61dc02 --- /dev/null +++ b/supervisor/dbus/manager.py @@ -0,0 +1,93 @@ +"""D-Bus interface objects.""" +import logging +from typing import List + +from ..const import SOCKET_DBUS +from ..coresys import CoreSys, CoreSysAttributes +from .agent import OSAgent +from .hostname import Hostname +from .interface import DBusInterface +from .logind import Logind +from .network import NetworkManager +from .rauc import Rauc +from .systemd import Systemd +from .timedate import TimeDate + +_LOGGER: logging.Logger = logging.getLogger(__name__) + + +class DBusManager(CoreSysAttributes): + """A DBus Interface handler.""" + + def __init__(self, coresys: CoreSys) -> None: + """Initialize D-Bus interface.""" + self.coresys: CoreSys = coresys + + self._systemd: Systemd = Systemd() + self._logind: Logind = Logind() + self._hostname: Hostname = Hostname() + self._rauc: Rauc = Rauc() + self._network: NetworkManager = NetworkManager() + self._agent: OSAgent = OSAgent() + self._timedate: TimeDate = TimeDate() + + @property + def systemd(self) -> Systemd: + """Return the systemd interface.""" + return self._systemd + + @property + def logind(self) -> Logind: + """Return the logind interface.""" + return self._logind + + @property + def hostname(self) -> Hostname: + """Return the hostname interface.""" + return self._hostname + + @property + def rauc(self) -> Rauc: + """Return the rauc interface.""" + return self._rauc + + @property + def network(self) -> NetworkManager: + """Return NetworkManager interface.""" + return self._network + + @property + def agent(self) -> OSAgent: + """Return OS-Agent interface.""" + return self._agent + + @property + def timedate(self) -> TimeDate: + """Return the timedate interface.""" + return self._timedate + + async def load(self) -> None: + """Connect interfaces to D-Bus.""" + if not SOCKET_DBUS.exists(): + _LOGGER.error( + "No D-Bus support on Host. Disabled any kind of host control!" + ) + return + + dbus_loads: List[DBusInterface] = [ + self.agent, + self.systemd, + self.logind, + self.hostname, + self.timedate, + self.network, + self.rauc, + ] + for dbus in dbus_loads: + _LOGGER.info("Load dbus interface %s", dbus.name) + try: + await dbus.connect() + except Exception as err: # pylint: disable=broad-except + _LOGGER.warning("Can't load dbus interface %s: %s", dbus.name, err) + + self.sys_host.supported_features.cache_clear() diff --git a/supervisor/host/__init__.py b/supervisor/host/__init__.py index 5b384fa5e..329b203df 100644 --- a/supervisor/host/__init__.py +++ b/supervisor/host/__init__.py @@ -4,10 +4,10 @@ from functools import lru_cache import logging from typing import List -from ..const import HostFeature from ..coresys import CoreSys, CoreSysAttributes from ..exceptions import HassioError, PulseAudioError from .apparmor import AppArmorControl +from .const import HostFeature from .control import SystemControl from .info import InfoCenter from .network import NetworkManager @@ -85,8 +85,11 @@ class HostManager(CoreSysAttributes): if self.sys_dbus.timedate.is_connected: features.append(HostFeature.TIMEDATE) + if self.sys_dbus.agent.is_connected: + features.append(HostFeature.AGENT) + if self.sys_hassos.available: - features.append(HostFeature.HASSOS) + features.append(HostFeature.HAOS) return features @@ -100,6 +103,9 @@ class HostManager(CoreSysAttributes): if self.sys_dbus.network.is_connected: await self.network.update() + if self.sys_dbus.agent.is_connected: + await self.sys_dbus.agent.update() + with suppress(PulseAudioError): await self.sound.update() diff --git a/supervisor/host/const.py b/supervisor/host/const.py index d8762c380..1d41dd8dd 100644 --- a/supervisor/host/const.py +++ b/supervisor/host/const.py @@ -33,3 +33,16 @@ class WifiMode(str, Enum): MESH = "mesh" ADHOC = "adhoc" AP = "ap" + + +class HostFeature(str, Enum): + """Host feature.""" + + HAOS = "haos" + HOSTNAME = "hostname" + NETWORK = "network" + REBOOT = "reboot" + SERVICES = "services" + SHUTDOWN = "shutdown" + AGENT = "agent" + TIMEDATE = "timedate" diff --git a/supervisor/misc/tasks.py b/supervisor/misc/tasks.py index f405b2c8b..7a515f448 100644 --- a/supervisor/misc/tasks.py +++ b/supervisor/misc/tasks.py @@ -1,7 +1,7 @@ """A collection of tasks.""" import logging -from ..const import AddonState, HostFeature +from ..const import AddonState from ..coresys import CoreSysAttributes from ..exceptions import ( AddonsError, @@ -12,6 +12,7 @@ from ..exceptions import ( MulticastError, ObserverError, ) +from ..host.const import HostFeature from ..jobs.decorator import Job, JobCondition _LOGGER: logging.Logger = logging.getLogger(__name__) diff --git a/supervisor/resolution/evaluations/network_manager.py b/supervisor/resolution/evaluations/network_manager.py index 24984a205..8c92ec7a5 100644 --- a/supervisor/resolution/evaluations/network_manager.py +++ b/supervisor/resolution/evaluations/network_manager.py @@ -1,8 +1,9 @@ """Evaluation class for network manager.""" from typing import List -from ...const import CoreState, HostFeature +from ...const import CoreState from ...coresys import CoreSys +from ...host.const import HostFeature from ..const import UnsupportedReason from .base import EvaluateBase diff --git a/supervisor/resolution/evaluations/systemd.py b/supervisor/resolution/evaluations/systemd.py index 716958e03..2501c9232 100644 --- a/supervisor/resolution/evaluations/systemd.py +++ b/supervisor/resolution/evaluations/systemd.py @@ -1,8 +1,9 @@ """Evaluation class for systemd.""" from typing import List -from ...const import CoreState, HostFeature +from ...const import CoreState from ...coresys import CoreSys +from ...host.const import HostFeature from ..const import UnsupportedReason from .base import EvaluateBase diff --git a/supervisor/utils/gdbus.py b/supervisor/utils/gdbus.py index d900a21b1..6766d8a96 100644 --- a/supervisor/utils/gdbus.py +++ b/supervisor/utils/gdbus.py @@ -67,6 +67,7 @@ MONITOR: str = "gdbus monitor --system --dest {bus}" WAIT: str = "gdbus wait --system --activate {bus} --timeout 5 {bus}" DBUS_METHOD_GETALL: str = "org.freedesktop.DBus.Properties.GetAll" +DBUS_METHOD_SET: str = "org.freedesktop.DBus.Properties.Set" def _convert_bytes(value: str) -> str: @@ -225,6 +226,16 @@ class DBus: _LOGGER.error("No attributes returned for %s", interface) raise DBusFatalError() from err + async def set_property( + self, interface: str, name: str, value: Any + ) -> Dict[str, Any]: + """Set a property from interface.""" + try: + return (await self.call_dbus(DBUS_METHOD_SET, interface, name, value))[0] + except IndexError as err: + _LOGGER.error("No Set attribute %s for %s", name, interface) + raise DBusFatalError() from err + async def _send(self, command: List[str], silent=False) -> str: """Send command over dbus.""" # Run command diff --git a/tests/dbus/agent/__init__.py b/tests/dbus/agent/__init__.py new file mode 100644 index 000000000..c17dfc96c --- /dev/null +++ b/tests/dbus/agent/__init__.py @@ -0,0 +1 @@ +"""Tests for OS-Agent.""" diff --git a/tests/dbus/agent/test_agent.py b/tests/dbus/agent/test_agent.py new file mode 100644 index 000000000..87befe755 --- /dev/null +++ b/tests/dbus/agent/test_agent.py @@ -0,0 +1,15 @@ +"""Test OSAgent dbus interface.""" + +from supervisor.coresys import CoreSys + + +async def test_dbus_osagent(coresys: CoreSys): + """Test coresys dbus connection.""" + assert coresys.dbus.agent.version is None + assert coresys.dbus.agent.diagnostics is None + + await coresys.dbus.agent.connect() + await coresys.dbus.agent.update() + + assert coresys.dbus.agent.version == "1.1.0" + assert coresys.dbus.agent.diagnostics diff --git a/tests/dbus/agent/test_apparmor.py b/tests/dbus/agent/test_apparmor.py new file mode 100644 index 000000000..a825a56b0 --- /dev/null +++ b/tests/dbus/agent/test_apparmor.py @@ -0,0 +1,53 @@ +"""Test AppArmor/Agent dbus interface.""" +from pathlib import Path + +import pytest + +from supervisor.coresys import CoreSys +from supervisor.exceptions import DBusNotConnectedError + + +async def test_dbus_osagent_apparmor(coresys: CoreSys): + """Test coresys dbus connection.""" + assert coresys.dbus.agent.apparmor.version is None + + await coresys.dbus.agent.connect() + await coresys.dbus.agent.update() + + assert coresys.dbus.agent.apparmor.version == "2.13.2" + + +async def test_dbus_osagent_apparmor_load(coresys: CoreSys): + """Load AppArmor Profile on host.""" + + with pytest.raises(DBusNotConnectedError): + await coresys.dbus.agent.apparmor.load_profile( + Path("/data/apparmor/profile"), Path("/data/apparmor/cache") + ) + + await coresys.dbus.agent.connect() + + assert ( + await coresys.dbus.agent.apparmor.load_profile( + Path("/data/apparmor/profile"), Path("/data/apparmor/cache") + ) + is None + ) + + +async def test_dbus_osagent_apparmor_unload(coresys: CoreSys): + """Unload AppArmor Profile on host.""" + + with pytest.raises(DBusNotConnectedError): + await coresys.dbus.agent.apparmor.unload_profile( + Path("/data/apparmor/profile"), Path("/data/apparmor/cache") + ) + + await coresys.dbus.agent.connect() + + assert ( + await coresys.dbus.agent.apparmor.unload_profile( + Path("/data/apparmor/profile"), Path("/data/apparmor/cache") + ) + is None + ) diff --git a/tests/dbus/agent/test_cgroup.py b/tests/dbus/agent/test_cgroup.py new file mode 100644 index 000000000..583dfedf0 --- /dev/null +++ b/tests/dbus/agent/test_cgroup.py @@ -0,0 +1,20 @@ +"""Test CGroup/Agent dbus interface.""" + +import pytest + +from supervisor.coresys import CoreSys +from supervisor.exceptions import DBusNotConnectedError + + +async def test_dbus_osagent_cgroup_add_devices(coresys: CoreSys): + """Test wipe data partition on host.""" + + with pytest.raises(DBusNotConnectedError): + await coresys.dbus.agent.cgroup.add_devices_allowed("9324kl23j4kl", "*:* rwm") + + await coresys.dbus.agent.connect() + + assert ( + await coresys.dbus.agent.cgroup.add_devices_allowed("9324kl23j4kl", "*:* rwm") + is None + ) diff --git a/tests/dbus/agent/test_datadisk.py b/tests/dbus/agent/test_datadisk.py new file mode 100644 index 000000000..a0d72b5c4 --- /dev/null +++ b/tests/dbus/agent/test_datadisk.py @@ -0,0 +1,28 @@ +"""Test Datadisk/Agent dbus interface.""" +from pathlib import Path + +import pytest + +from supervisor.coresys import CoreSys +from supervisor.exceptions import DBusNotConnectedError + + +async def test_dbus_osagent_datadisk(coresys: CoreSys): + """Test coresys dbus connection.""" + assert coresys.dbus.agent.datadisk.current_device is None + + await coresys.dbus.agent.connect() + await coresys.dbus.agent.update() + + assert coresys.dbus.agent.datadisk.current_device.as_posix() == "/dev/sda" + + +async def test_dbus_osagent_datadisk_change_device(coresys: CoreSys): + """Change datadisk on device.""" + + with pytest.raises(DBusNotConnectedError): + await coresys.dbus.agent.datadisk.change_device(Path("/dev/sdb")) + + await coresys.dbus.agent.connect() + + assert await coresys.dbus.agent.datadisk.change_device(Path("/dev/sdb")) is None diff --git a/tests/dbus/agent/test_system.py b/tests/dbus/agent/test_system.py new file mode 100644 index 000000000..63fc67b29 --- /dev/null +++ b/tests/dbus/agent/test_system.py @@ -0,0 +1,17 @@ +"""Test System/Agent dbus interface.""" + +import pytest + +from supervisor.coresys import CoreSys +from supervisor.exceptions import DBusNotConnectedError + + +async def test_dbus_osagent_system_wipe(coresys: CoreSys): + """Test wipe data partition on host.""" + + with pytest.raises(DBusNotConnectedError): + await coresys.dbus.agent.system.schedule_wipe_device() + + await coresys.dbus.agent.connect() + + assert await coresys.dbus.agent.system.schedule_wipe_device() is None diff --git a/tests/dbus/network/__init__.py b/tests/dbus/network/__init__.py new file mode 100644 index 000000000..e46cba1a9 --- /dev/null +++ b/tests/dbus/network/__init__.py @@ -0,0 +1 @@ +"""Tests for DBUS NetworkManager.""" diff --git a/tests/fixtures/io_hass_os.json b/tests/fixtures/io_hass_os.json new file mode 100644 index 000000000..af7eab57e --- /dev/null +++ b/tests/fixtures/io_hass_os.json @@ -0,0 +1 @@ +{ "Version": "1.1.0", "Diagnostics": true } diff --git a/tests/fixtures/io_hass_os.xml b/tests/fixtures/io_hass_os.xml new file mode 100644 index 000000000..06cff9ea7 --- /dev/null +++ b/tests/fixtures/io_hass_os.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/io_hass_os_AppArmor-LoadProfile.fixture b/tests/fixtures/io_hass_os_AppArmor-LoadProfile.fixture new file mode 100644 index 000000000..3c2c10f15 --- /dev/null +++ b/tests/fixtures/io_hass_os_AppArmor-LoadProfile.fixture @@ -0,0 +1 @@ +(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.fixture b/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.fixture new file mode 100644 index 000000000..3c2c10f15 --- /dev/null +++ b/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.fixture @@ -0,0 +1 @@ +(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_AppArmor.json b/tests/fixtures/io_hass_os_AppArmor.json new file mode 100644 index 000000000..57632f0f6 --- /dev/null +++ b/tests/fixtures/io_hass_os_AppArmor.json @@ -0,0 +1 @@ +{ "ParserVersion": "2.13.2" } diff --git a/tests/fixtures/io_hass_os_AppArmor.xml b/tests/fixtures/io_hass_os_AppArmor.xml new file mode 100644 index 000000000..0cd2178a2 --- /dev/null +++ b/tests/fixtures/io_hass_os_AppArmor.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.fixture b/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.fixture new file mode 100644 index 000000000..3c2c10f15 --- /dev/null +++ b/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.fixture @@ -0,0 +1 @@ +(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_CGroup.xml b/tests/fixtures/io_hass_os_CGroup.xml new file mode 100644 index 000000000..9ce67937d --- /dev/null +++ b/tests/fixtures/io_hass_os_CGroup.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.fixture b/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.fixture new file mode 100644 index 000000000..3c2c10f15 --- /dev/null +++ b/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.fixture @@ -0,0 +1 @@ +(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_DataDisk.json b/tests/fixtures/io_hass_os_DataDisk.json new file mode 100644 index 000000000..1599cc0fc --- /dev/null +++ b/tests/fixtures/io_hass_os_DataDisk.json @@ -0,0 +1 @@ +{ "CurrentDevice": "/dev/sda" } diff --git a/tests/fixtures/io_hass_os_DataDisk.xml b/tests/fixtures/io_hass_os_DataDisk.xml new file mode 100644 index 000000000..9c64e9a2d --- /dev/null +++ b/tests/fixtures/io_hass_os_DataDisk.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.fixture b/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.fixture new file mode 100644 index 000000000..3c2c10f15 --- /dev/null +++ b/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.fixture @@ -0,0 +1 @@ +(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_System.xml b/tests/fixtures/io_hass_os_System.xml new file mode 100644 index 000000000..43030956f --- /dev/null +++ b/tests/fixtures/io_hass_os_System.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/resolution/evaluation/test_evaluate_systemd.py b/tests/resolution/evaluation/test_evaluate_systemd.py index 654b42245..5a79c4ca5 100644 --- a/tests/resolution/evaluation/test_evaluate_systemd.py +++ b/tests/resolution/evaluation/test_evaluate_systemd.py @@ -2,8 +2,9 @@ # pylint: disable=import-error,protected-access from unittest.mock import MagicMock, patch -from supervisor.const import CoreState, HostFeature +from supervisor.const import CoreState from supervisor.coresys import CoreSys +from supervisor.host.const import HostFeature from supervisor.resolution.evaluations.systemd import EvaluateSystemd