Skip to content

Commit

Permalink
Add resolution center support
Browse files Browse the repository at this point in the history
  • Loading branch information
mdegat01 committed Sep 19, 2024
1 parent 7819a28 commit dfd7f8d
Show file tree
Hide file tree
Showing 7 changed files with 475 additions and 0 deletions.
24 changes: 24 additions & 0 deletions aiohasupervisor/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@
StoreInfo,
SupervisorRole,
)
from aiohasupervisor.models.resolution import (
Check,
CheckOptions,
CheckType,
ContextType,
Issue,
IssueType,
ResolutionInfo,
Suggestion,
SuggestionType,
UnhealthyReason,
UnsupportedReason,
)
from aiohasupervisor.models.root import (
AvailableUpdate,
HostFeature,
Expand Down Expand Up @@ -62,4 +75,15 @@
"StoreInfo",
"StoreAddonUpdate",
"StoreAddRepository",
"Check",
"CheckOptions",
"CheckType",
"ContextType",
"Issue",
"IssueType",
"ResolutionInfo",
"Suggestion",
"SuggestionType",
"UnhealthyReason",
"UnsupportedReason",
]
210 changes: 210 additions & 0 deletions aiohasupervisor/models/resolution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
"""Models for resolution center APIs."""

from dataclasses import dataclass
from enum import StrEnum
from uuid import UUID

from .base import Options, ResponseData

# --- ENUMS ----


class SuggestionType(StrEnum):
"""SuggestionType type.
This is an incomplete list. Supervisor regularly adds new types of suggestions as
they are discovered. Therefore when returning an suggestion, it may have a type that
is not in this list parsed as strings on older versions of the client.
"""

ADOPT_DATA_DISK = "adopt_data_disk"
CLEAR_FULL_BACKUP = "clear_full_backup"
CREATE_FULL_BACKUP = "create_full_backup"
EXECUTE_INTEGRITY = "execute_integrity"
EXECUTE_REBOOT = "execute_reboot"
EXECUTE_REBUILD = "execute_rebuild"
EXECUTE_RELOAD = "execute_reload"
EXECUTE_REMOVE = "execute_remove"
EXECUTE_REPAIR = "execute_repair"
EXECUTE_RESET = "execute_reset"
EXECUTE_STOP = "execute_stop"
EXECUTE_UPDATE = "execute_update"
REGISTRY_LOGIN = "registry_login"
RENAME_DATA_DISK = "rename_data_disk"


class IssueType(StrEnum):
"""IssueType type.
This is an incomplete list. Supervisor regularly adds new types of issues as they
are discovered. Therefore when returning an issue, it may have a type that is not
in this list parsed as strings on older versions of the client.
"""

CORRUPT_DOCKER = "corrupt_docker"
CORRUPT_REPOSITORY = "corrupt_repository"
CORRUPT_FILESYSTEM = "corrupt_filesystem"
DETACHED_ADDON_MISSING = "detached_addon_missing"
DETACHED_ADDON_REMOVED = "detached_addon_removed"
DISABLED_DATA_DISK = "disabled_data_disk"
DNS_LOOP = "dns_loop"
DNS_SERVER_FAILED = "dns_server_failed"
DNS_SERVER_IPV6_ERROR = "dns_server_ipv6_error"
DOCKER_CONFIG = "docker_config"
DOCKER_RATELIMIT = "docker_ratelimit"
FATAL_ERROR = "fatal_error"
FREE_SPACE = "free_space"
IPV4_CONNECTION_PROBLEM = "ipv4_connection_problem"
MISSING_IMAGE = "missing_image"
MOUNT_FAILED = "mount_failed"
MULTIPLE_DATA_DISKS = "multiple_data_disks"
NO_CURRENT_BACKUP = "no_current_backup"
PWNED = "pwned"
REBOOT_REQUIRED = "reboot_required"
SECURITY = "security"
TRUST = "trust"
UPDATE_FAILED = "update_failed"
UPDATE_ROLLBACK = "update_rollback"


class UnsupportedReason(StrEnum):
"""UnsupportedReason type.
This is an incomplete list. Supervisor regularly adds new unsupported
reasons as they are discovered. Therefore when returning a list of unsupported
reasons, some may not be in this list parsed as strings on older versions of the
client.
"""

APPARMOR = "apparmor"
CGROUP_VERSION = "cgroup_version"
CONNECTIVITY_CHECK = "connectivity_check"
CONTENT_TRUST = "content_trust"
DBUS = "dbus"
DNS_SERVER = "dns_server"
DOCKER_CONFIGURATION = "docker_configuration"
DOCKER_VERSION = "docker_version"
JOB_CONDITIONS = "job_conditions"
LXC = "lxc"
NETWORK_MANAGER = "network_manager"
OS = "os"
OS_AGENT = "os_agent"
PRIVILEGED = "privileged"
RESTART_POLICY = "restart_policy"
SOFTWARE = "software"
SOURCE_MODS = "source_mods"
SUPERVISOR_VERSION = "supervisor_version"
SYSTEMD = "systemd"
SYSTEMD_JOURNAL = "systemd_journal"
SYSTEMD_RESOLVED = "systemd_resolved"
VIRTUALIZATION_IMAGE = "virtualization_image"


class UnhealthyReason(StrEnum):
"""UnhealthyReason type.
This is an incomplete list. Supervisor regularly adds new unhealthy
reasons as they are discovered. Therefore when returning a list of unhealthy
reasons, some may not be in this list parsed as strings on older versions of the
client.
"""

DOCKER = "docker"
OSERROR_BAD_MESSAGE = "oserror_bad_message"
PRIVILEGED = "privileged"
SUPERVISOR = "supervisor"
SETUP = "setup"
UNTRUSTED = "untrusted"


class ContextType(StrEnum):
"""ContextType type."""

ADDON = "addon"
CORE = "core"
DNS_SERVER = "dns_server"
MOUNT = "mount"
OS = "os"
PLUGIN = "plugin"
SUPERVISOR = "supervisor"
STORE = "store"
SYSTEM = "system"


class CheckType(StrEnum):
"""CheckType type.
This is an incomplete list. Supervisor regularly adds new checks as they are
discovered. Therefore when returning a list of checks, some may have a type that is
not in this list parsed as strings on older versions of the client.
"""

ADDON_PWNED = "addon_pwned"
BACKUPS = "backups"
CORE_SECURITY = "core_security"
DETACHED_ADDON_MISSING = "detached_addon_missing"
DETACHED_ADDON_REMOVED = "detached_addon_removed"
DISABLED_DATA_DISK = "disabled_data_disk"
DNS_SERVER_IPV6 = "dns_server_ipv6"
DNS_SERVER = "dns_server"
DOCKER_CONFIG = "docker_config"
FREE_SPACE = "free_space"
MULTIPLE_DATA_DISKS = "multiple_data_disks"
NETWORK_INTERFACE_IPV4 = "network_interface_ipv4"
SUPERVISOR_TRUST = "supervisor_trust"


# --- OBJECTS ----


@dataclass(frozen=True, slots=True)
class Suggestion(ResponseData):
"""Suggestion model."""

type: SuggestionType | str
context: ContextType
reference: str | None
uuid: UUID
auto: bool


@dataclass(frozen=True, slots=True)
class Issue(ResponseData):
"""Issue model."""

type: IssueType | str
context: ContextType
reference: str | None
uuid: UUID


@dataclass(frozen=True, slots=True)
class Check(ResponseData):
"""Check model."""

enabled: bool
slug: CheckType | str


@dataclass(frozen=True, slots=True)
class SuggestionsList(ResponseData):
"""SuggestionsList model."""

suggestions: list[Suggestion]


@dataclass(frozen=True, slots=True)
class ResolutionInfo(SuggestionsList, ResponseData):
"""ResolutionInfo model."""

unsupported: list[UnsupportedReason | str]
unhealthy: list[UnhealthyReason | str]
issues: list[Issue]
checks: list[Check]


@dataclass(frozen=True, slots=True)
class CheckOptions(Options):
"""CheckOptions model."""

enabled: bool | None = None
54 changes: 54 additions & 0 deletions aiohasupervisor/resolution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Resolution center client for supervisor."""

from uuid import UUID

from .client import _SupervisorComponentClient
from .models.resolution import (
CheckOptions,
CheckType,
ResolutionInfo,
Suggestion,
SuggestionsList,
)


class ResolutionClient(_SupervisorComponentClient):
"""Handles resolution center access in supervisor."""

async def info(self) -> ResolutionInfo:
"""Get resolution center info."""
result = await self._client.get("resolution/info")
return ResolutionInfo.from_dict(result.data)

async def check_options(
self, check: CheckType | str, options: CheckOptions
) -> None:
"""Set options for a check."""
await self._client.post(
f"resolution/check/{check}/options", json=options.to_dict()
)

async def run_check(self, check: CheckType | str) -> None:
"""Run a check."""
await self._client.post(f"resolution/check/{check}/run")

async def apply_suggestion(self, suggestion: UUID) -> None:
"""Apply a suggestion."""
await self._client.post(f"resolution/suggestion/{suggestion.hex}")

async def dismiss_suggestion(self, suggestion: UUID) -> None:
"""Dismiss a suggestion."""
await self._client.delete(f"resolution/suggestion/{suggestion.hex}")

async def dismiss_issue(self, issue: UUID) -> None:
"""Dismiss an issue."""
await self._client.delete(f"resolution/issue/{issue.hex}")

async def suggestions_for_issue(self, issue: UUID) -> list[Suggestion]:
"""Get suggestions for issue."""
result = await self._client.get(f"resolution/issue/{issue.hex}/suggestions")
return SuggestionsList.from_dict(result.data).suggestions

async def healthcheck(self) -> None:
"""Run a healthcheck."""
await self._client.post("resolution/healthcheck")
7 changes: 7 additions & 0 deletions aiohasupervisor/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .addons import AddonsClient
from .client import _SupervisorClient
from .models.root import AvailableUpdate, AvailableUpdates, RootInfo
from .resolution import ResolutionClient
from .store import StoreClient


Expand All @@ -23,13 +24,19 @@ def __init__(
"""Initialize client."""
self._client = _SupervisorClient(api_host, token, request_timeout, session)
self._addons = AddonsClient(self._client)
self._resolution = ResolutionClient(self._client)
self._store = StoreClient(self._client)

@property
def addons(self) -> AddonsClient:
"""Get addons component client."""
return self._addons

@property
def resolution(self) -> ResolutionClient:
"""Get resolution center component client."""
return self._resolution

@property
def store(self) -> StoreClient:
"""Get store component client."""
Expand Down
39 changes: 39 additions & 0 deletions tests/fixtures/resolution_info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"result": "ok",
"data": {
"unsupported": [],
"unhealthy": ["supervisor"],
"suggestions": [
{
"type": "create_full_backup",
"context": "system",
"reference": null,
"uuid": "f87d3556f02c4004a47111c072c76fac",
"auto": false
}
],
"issues": [
{
"type": "no_current_backup",
"context": "system",
"reference": null,
"uuid": "7f0eac2b61c9456dab6970507a276c36"
}
],
"checks": [
{ "enabled": true, "slug": "dns_server_ipv6" },
{ "enabled": true, "slug": "disabled_data_disk" },
{ "enabled": true, "slug": "detached_addon_missing" },
{ "enabled": true, "slug": "multiple_data_disks" },
{ "enabled": true, "slug": "backups" },
{ "enabled": true, "slug": "supervisor_trust" },
{ "enabled": true, "slug": "network_interface_ipv4" },
{ "enabled": true, "slug": "dns_server" },
{ "enabled": true, "slug": "free_space" },
{ "enabled": true, "slug": "detached_addon_removed" },
{ "enabled": true, "slug": "docker_config" },
{ "enabled": true, "slug": "addon_pwned" },
{ "enabled": true, "slug": "core_security" }
]
}
}
14 changes: 14 additions & 0 deletions tests/fixtures/resolution_suggestions_for_issue.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"result": "ok",
"data": {
"suggestions": [
{
"type": "create_full_backup",
"context": "system",
"reference": null,
"uuid": "f87d3556f02c4004a47111c072c76fac",
"auto": false
}
]
}
}
Loading

0 comments on commit dfd7f8d

Please sign in to comment.