from collections.abc import Mapping from dataclasses import dataclass, field from datetime import datetime, timezone from enum import StrEnum from typing import Any DNS_RECORDS = "dns_records" PROXY_HOSTS = "proxy_hosts" CERTIFICATES = "certificates" class CapabilityOperation(StrEnum): DISCOVER = "discover" APPLY = "apply" VERIFY = "verify" class PlanOperation(StrEnum): CREATE = "create" UPDATE = "update" DELETE = "delete" NOOP = "noop" SKIP = "skip" @dataclass(frozen=True, order=True) class Capability: object_type: str operation: CapabilityOperation def __post_init__(self) -> None: object_type = self.object_type.strip().lower() if not object_type: raise ValueError("Capability object_type cannot be empty") object.__setattr__(self, "object_type", object_type) @dataclass(frozen=True) class Endpoint: plugin: str address: str label: str | None = None def __post_init__(self) -> None: plugin = self.plugin.strip().lower() address = self.address.strip().rstrip("/").lower() if not plugin: raise ValueError("Endpoint plugin cannot be empty") if not address: raise ValueError("Endpoint address cannot be empty") object.__setattr__(self, "plugin", plugin) object.__setattr__(self, "address", address) @property def identity(self) -> str: return f"{self.plugin}://{self.address}" @dataclass(frozen=True) class DiscoveredObject: object_type: str local_key: str payload: Mapping[str, Any] @dataclass(frozen=True) class NormalizedObject: object_type: str logical_key: str payload: Mapping[str, Any] local_key: str | None = None @dataclass(frozen=True) class Observation: endpoint_id: str object: NormalizedObject observed_at: datetime = field( default_factory=lambda: datetime.now(timezone.utc) ) @dataclass(frozen=True) class PlanAction: endpoint_id: str operation: PlanOperation desired: NormalizedObject | None = None current: NormalizedObject | None = None reason: str | None = None @dataclass(frozen=True) class EndpointPlan: endpoint_id: str actions: tuple[PlanAction, ...]