LibclusterMesh.TailscaleStatus (libcluster_mesh v0.1.0)

Copy Markdown View Source

Parses tailscale status --json output into LibclusterMesh.Peer structs.

Only the Peer map is consulted; the local node (Self) is not a connection target. Peers without an IPv4 mesh address are skipped.

Summary

Functions

Parses the JSON status output. Never raises: peers with missing or unexpected fields are skipped, and undecodable input is surfaced as {:error, message}.

The name this node answers to on the mesh, or nil.

Functions

parse_peers(json)

@spec parse_peers(String.t()) ::
  {:ok, [LibclusterMesh.Peer.t()]} | {:error, String.t()}

Parses the JSON status output. Never raises: peers with missing or unexpected fields are skipped, and undecodable input is surfaced as {:error, message}.

iex> json = ~S({"Peer": {"nodekey:a": {"HostName": "myapp-1",
...>   "DNSName": "myapp-1.example.ts.net.", "Online": true,
...>   "TailscaleIPs": ["100.64.0.2"]}}})
iex> LibclusterMesh.TailscaleStatus.parse_peers(json)
{:ok, [%LibclusterMesh.Peer{ip: "100.64.0.2", hostname: "myapp-1", online?: true}]}

iex> LibclusterMesh.TailscaleStatus.parse_peers(~S({"Peer": null}))
{:ok, []}

parse_self(json)

@spec parse_self(String.t()) :: String.t() | nil

The name this node answers to on the mesh, or nil.

Self is not in the peer map — a node is not something to connect to — but it is the one entry a person is guaranteed to be looking at, so anything showing the mesh by name needs it separately.

iex> LibclusterMesh.TailscaleStatus.parse_self(~S({"Self": {"HostName": "myapp-1"}}))
"myapp-1"

iex> LibclusterMesh.TailscaleStatus.parse_self(~S({"Peer": null}))
nil