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
@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, []}
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