mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
101 lines
2.7 KiB
Python
101 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MeshSpec:
|
|
"""One declared mesh.
|
|
|
|
Args:
|
|
name: identifier of the mesh, used as the interface name.
|
|
subnet: the mesh address range, as ``a.b.c.0/24``.
|
|
listen_port: UDP port the hub, or every member of a full mesh,
|
|
listens on.
|
|
routed_range: the range a spoke routes through the hub.
|
|
"""
|
|
|
|
name: str
|
|
subnet: str
|
|
listen_port: int
|
|
routed_range: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Topology:
|
|
"""Who an inventory puts into each mesh.
|
|
|
|
Args:
|
|
hub: the host that routes for every mesh, or None for full meshes.
|
|
spokes: mesh name to the hosts that joined it, never the hub.
|
|
"""
|
|
|
|
hub: str | None
|
|
spokes: dict[str, tuple[str, ...]] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class MeshMember:
|
|
"""One host's place in a resolved mesh.
|
|
|
|
Args:
|
|
host: inventory hostname.
|
|
address: the member's address inside the mesh subnet.
|
|
endpoint: the underlay address its peers dial.
|
|
private_key: the member's own secret half, or None when the member
|
|
already holds one.
|
|
public_key: the half its peers receive.
|
|
is_hub: whether this member routes for the others.
|
|
"""
|
|
|
|
host: str
|
|
address: str
|
|
private_key: str | None
|
|
public_key: str
|
|
is_hub: bool
|
|
endpoint: str = ""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Mesh:
|
|
"""A spec resolved against an inventory.
|
|
|
|
Args:
|
|
spec: the declaration this mesh was resolved from.
|
|
members: every host in the mesh, hub first.
|
|
"""
|
|
|
|
spec: MeshSpec
|
|
members: tuple[MeshMember, ...] = field(default_factory=tuple)
|
|
|
|
@property
|
|
def hub(self) -> MeshMember:
|
|
for member in self.members:
|
|
if member.is_hub:
|
|
return member
|
|
raise ValueError(f"mesh '{self.spec.name}' resolved without a hub")
|
|
|
|
@property
|
|
def spokes(self) -> tuple[MeshMember, ...]:
|
|
return tuple(m for m in self.members if not m.is_hub)
|
|
|
|
@property
|
|
def is_full(self) -> bool:
|
|
return not any(member.is_hub for member in self.members)
|
|
|
|
def peers_of(self, host: str) -> tuple[MeshMember, ...]:
|
|
member = self.member(host)
|
|
if member is None:
|
|
return ()
|
|
if member.is_hub:
|
|
return self.spokes
|
|
if self.is_full:
|
|
return tuple(m for m in self.members if m.host != host)
|
|
return (self.hub,)
|
|
|
|
def member(self, host: str) -> MeshMember | None:
|
|
for candidate in self.members:
|
|
if candidate.host == host:
|
|
return candidate
|
|
return None
|