Skip to content

Commit 040a24b

Browse files
authored
Add Supervisor's OS APIs (#12)
* Add Supervisor OS APIs * Add all models to init all * Field as purepath in model * Remove `devices` from list data disk API * Add options test
1 parent d04e78c commit 040a24b

File tree

9 files changed

+450
-0
lines changed

9 files changed

+450
-0
lines changed

aiohasupervisor/models/__init__.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@
3232
HomeAssistantStopOptions,
3333
HomeAssistantUpdateOptions,
3434
)
35+
from aiohasupervisor.models.os import (
36+
BootSlot,
37+
BootSlotName,
38+
DataDisk,
39+
GreenInfo,
40+
GreenOptions,
41+
MigrateDataOptions,
42+
OSInfo,
43+
OSUpdate,
44+
RaucState,
45+
SetBootSlotOptions,
46+
YellowInfo,
47+
YellowOptions,
48+
)
3549
from aiohasupervisor.models.resolution import (
3650
Check,
3751
CheckOptions,
@@ -112,4 +126,16 @@
112126
"HomeAssistantStats",
113127
"HomeAssistantStopOptions",
114128
"HomeAssistantUpdateOptions",
129+
"RaucState",
130+
"BootSlotName",
131+
"BootSlot",
132+
"OSInfo",
133+
"OSUpdate",
134+
"MigrateDataOptions",
135+
"DataDisk",
136+
"SetBootSlotOptions",
137+
"GreenInfo",
138+
"GreenOptions",
139+
"YellowInfo",
140+
"YellowOptions",
115141
]

aiohasupervisor/models/os.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""Models for OS APIs."""
2+
3+
from dataclasses import dataclass
4+
from enum import StrEnum
5+
from pathlib import PurePath
6+
7+
from .base import Options, Request, ResponseData
8+
9+
# --- ENUMS ----
10+
11+
12+
class RaucState(StrEnum):
13+
"""RaucState type."""
14+
15+
GOOD = "good"
16+
BAD = "bad"
17+
ACTIVE = "active"
18+
19+
20+
class BootSlotName(StrEnum):
21+
"""BootSlotName type."""
22+
23+
A = "A"
24+
B = "B"
25+
26+
27+
# --- OBJECTS ----
28+
29+
30+
@dataclass(frozen=True, slots=True)
31+
class BootSlot(ResponseData):
32+
"""BootSlot model."""
33+
34+
state: str
35+
status: RaucState | None
36+
version: str | None
37+
38+
39+
@dataclass(frozen=True, slots=True)
40+
class OSInfo(ResponseData):
41+
"""OSInfo model."""
42+
43+
version: str | None
44+
version_latest: str | None
45+
update_available: bool
46+
board: str | None
47+
boot: str | None
48+
data_disk: str | None
49+
boot_slots: dict[str, BootSlot]
50+
51+
52+
@dataclass(frozen=True, slots=True)
53+
class OSUpdate(Request):
54+
"""OSUpdate model."""
55+
56+
version: str | None = None
57+
58+
59+
@dataclass(frozen=True, slots=True)
60+
class MigrateDataOptions(Request):
61+
"""MigrateDataOptions model."""
62+
63+
device: str
64+
65+
66+
@dataclass(frozen=True, slots=True)
67+
class DataDisk(ResponseData):
68+
"""DataDisk model."""
69+
70+
name: str
71+
vendor: str
72+
model: str
73+
serial: str
74+
size: int
75+
id: str
76+
dev_path: PurePath
77+
78+
79+
@dataclass(frozen=True, slots=True)
80+
class DataDiskList(ResponseData):
81+
"""DataDiskList model."""
82+
83+
disks: list[DataDisk]
84+
85+
86+
@dataclass(frozen=True, slots=True)
87+
class SetBootSlotOptions(Request):
88+
"""SetBootSlotOptions model."""
89+
90+
boot_slot: BootSlotName
91+
92+
93+
@dataclass(frozen=True, slots=True)
94+
class GreenInfo(ResponseData):
95+
"""GreenInfo model."""
96+
97+
activity_led: bool
98+
power_led: bool
99+
system_health_led: bool
100+
101+
102+
@dataclass(frozen=True, slots=True)
103+
class GreenOptions(Options):
104+
"""GreenOptions model."""
105+
106+
activity_led: bool | None = None
107+
power_led: bool | None = None
108+
system_health_led: bool | None = None
109+
110+
111+
@dataclass(frozen=True, slots=True)
112+
class YellowInfo(ResponseData):
113+
"""YellowInfo model."""
114+
115+
disk_led: bool
116+
heartbeat_led: bool
117+
power_led: bool
118+
119+
120+
@dataclass(frozen=True, slots=True)
121+
class YellowOptions(Options):
122+
"""YellowOptions model."""
123+
124+
disk_led: bool | None = None
125+
heartbeat_led: bool | None = None
126+
power_led: bool | None = None

aiohasupervisor/os.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""OS client for supervisor."""
2+
3+
from .client import _SupervisorComponentClient
4+
from .models.os import (
5+
DataDisk,
6+
DataDiskList,
7+
GreenInfo,
8+
GreenOptions,
9+
MigrateDataOptions,
10+
OSInfo,
11+
OSUpdate,
12+
SetBootSlotOptions,
13+
YellowInfo,
14+
YellowOptions,
15+
)
16+
17+
18+
class OSClient(_SupervisorComponentClient):
19+
"""Handles OS access in supervisor."""
20+
21+
async def info(self) -> OSInfo:
22+
"""Get OS info."""
23+
result = await self._client.get("os/info")
24+
return OSInfo.from_dict(result.data)
25+
26+
async def update(self, options: OSUpdate | None = None) -> None:
27+
"""Update OS."""
28+
await self._client.post(
29+
"os/update", json=options.to_dict() if options else None
30+
)
31+
32+
async def config_sync(self) -> None:
33+
"""Trigger config reload on OS."""
34+
await self._client.post("os/config/sync")
35+
36+
async def migrate_data(self, options: MigrateDataOptions) -> None:
37+
"""Migrate data to new data disk and reboot."""
38+
await self._client.post("os/datadisk/move", json=options.to_dict())
39+
40+
async def list_data_disks(self) -> list[DataDisk]:
41+
"""Get all data disks."""
42+
result = await self._client.get("os/datadisk/list")
43+
return DataDiskList.from_dict(result.data).disks
44+
45+
async def wipe_data(self) -> None:
46+
"""Trigger data disk wipe on host and reboot."""
47+
await self._client.post("os/datadisk/wipe")
48+
49+
async def set_boot_slot(self, options: SetBootSlotOptions) -> None:
50+
"""Change active boot slot on host and reboot."""
51+
await self._client.post("os/boot-slot", json=options.to_dict())
52+
53+
async def green_info(self) -> GreenInfo:
54+
"""Get info for green board (if in use)."""
55+
result = await self._client.get("os/boards/green")
56+
return GreenInfo.from_dict(result.data)
57+
58+
async def green_options(self, options: GreenOptions) -> None:
59+
"""Set options for green board (if in use)."""
60+
await self._client.post("os/boards/green", json=options.to_dict())
61+
62+
async def yellow_info(self) -> YellowInfo:
63+
"""Get info for yellow board (if in use)."""
64+
result = await self._client.get("os/boards/yellow")
65+
return YellowInfo.from_dict(result.data)
66+
67+
async def yellow_options(self, options: YellowOptions) -> None:
68+
"""Set options for yellow board (if in use)."""
69+
await self._client.post("os/boards/yellow", json=options.to_dict())

aiohasupervisor/root.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .client import _SupervisorClient
99
from .homeassistant import HomeAssistantClient
1010
from .models.root import AvailableUpdate, AvailableUpdates, RootInfo
11+
from .os import OSClient
1112
from .resolution import ResolutionClient
1213
from .store import StoreClient
1314
from .supervisor import SupervisorManagementClient
@@ -26,6 +27,7 @@ def __init__(
2627
"""Initialize client."""
2728
self._client = _SupervisorClient(api_host, token, request_timeout, session)
2829
self._addons = AddonsClient(self._client)
30+
self._os = OSClient(self._client)
2931
self._resolution = ResolutionClient(self._client)
3032
self._store = StoreClient(self._client)
3133
self._supervisor = SupervisorManagementClient(self._client)
@@ -41,6 +43,11 @@ def homeassistant(self) -> HomeAssistantClient:
4143
"""Get Home Assistant component client."""
4244
return self._homeassistant
4345

46+
@property
47+
def os(self) -> OSClient:
48+
"""Get OS component client."""
49+
return self._os
50+
4451
@property
4552
def resolution(self) -> ResolutionClient:
4653
"""Get resolution center component client."""
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"result": "ok",
3+
"data": {
4+
"devices": ["SSK-SSK-Storage-DF123"],
5+
"disks": [
6+
{
7+
"name": "SSK SSK Storage (DF123)",
8+
"vendor": "SSK",
9+
"model": "SSK Storage",
10+
"serial": "DF123",
11+
"size": 250059350016,
12+
"id": "SSK-SSK-Storage-DF123",
13+
"dev_path": "/dev/sda"
14+
}
15+
]
16+
}
17+
}

tests/fixtures/os_green_info.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"result": "ok",
3+
"data": { "activity_led": true, "power_led": true, "system_health_led": true }
4+
}

tests/fixtures/os_info.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"result": "ok",
3+
"data": {
4+
"version": "13.0",
5+
"version_latest": "13.1",
6+
"update_available": true,
7+
"board": "odroid-n2",
8+
"boot": "B",
9+
"data_disk": "BJTD4R-0xaabbccdd",
10+
"boot_slots": {
11+
"A": { "state": "inactive", "status": "good", "version": null },
12+
"B": {
13+
"state": "booted",
14+
"status": "good",
15+
"version": "13.0"
16+
}
17+
}
18+
}
19+
}

tests/fixtures/os_yellow_info.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"result": "ok",
3+
"data": { "disk_led": true, "heartbeat_led": true, "power_led": true }
4+
}

0 commit comments

Comments
 (0)