wingbits client and cli helpers for mgw310 and other devices
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/readsb"
|
||||
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/wingbits"
|
||||
)
|
||||
|
||||
// Aircraft fetches and decodes aircraft.json. When one or more filters are
|
||||
// supplied, only matching aircraft are retained in the returned report.
|
||||
func (c *Client) Aircraft(ctx context.Context, filters ...readsb.AircraftFilter) (*readsb.AircraftReport, error) {
|
||||
ep, port := c.readsbEndpoint()
|
||||
report, err := getJSON[readsb.AircraftReport](ctx, c, ep.url(port, true, "aircraft.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
report.Aircraft = report.Filter(filters...)
|
||||
return report, nil
|
||||
}
|
||||
|
||||
// Receiver fetches and decodes receiver.json.
|
||||
func (c *Client) Receiver(ctx context.Context) (*readsb.Receiver, error) {
|
||||
ep, port := c.readsbEndpoint()
|
||||
return getJSON[readsb.Receiver](ctx, c, ep.url(port, true, "receiver.json"))
|
||||
}
|
||||
|
||||
// Stats fetches and decodes stats.json.
|
||||
func (c *Client) Stats(ctx context.Context) (*readsb.Stats, error) {
|
||||
ep, port := c.readsbEndpoint()
|
||||
return getJSON[readsb.Stats](ctx, c, ep.url(port, true, "stats.json"))
|
||||
}
|
||||
|
||||
// Outline fetches and decodes outline.json (the receiver range polygon). It is
|
||||
// only served by the tar1090 root, so a tar1090 endpoint must be configured.
|
||||
func (c *Client) Outline(ctx context.Context) (*readsb.Outline, error) {
|
||||
if c.tar1090 == nil {
|
||||
return nil, fmt.Errorf("wingbits: outline.json requires a tar1090 endpoint")
|
||||
}
|
||||
return getJSON[readsb.Outline](ctx, c, c.tar1090.url(DefaultTar1090Port, true, "outline.json"))
|
||||
}
|
||||
|
||||
// Diagnostics fetches and decodes /network/diagnostics from the Wingbits root.
|
||||
func (c *Client) Diagnostics(ctx context.Context) (*wingbits.Diagnostics, error) {
|
||||
return getJSON[wingbits.Diagnostics](ctx, c, c.wingbits.url(DefaultWingbitsPort, false, "network", "diagnostics"))
|
||||
}
|
||||
|
||||
// Status fetches and decodes the Tailscale status from the Wingbits root.
|
||||
func (c *Client) Status(ctx context.Context) (*wingbits.TailscaleStatus, error) {
|
||||
return getJSON[wingbits.TailscaleStatus](ctx, c, c.wingbits.url(DefaultWingbitsPort, false, "tailscale", "status"))
|
||||
}
|
||||
|
||||
// Metrics fetches /metrics from the Wingbits root and parses the Prometheus
|
||||
// text exposition format into structured metric families.
|
||||
func (c *Client) Metrics(ctx context.Context) (*wingbits.Metrics, error) {
|
||||
body, err := c.get(ctx, c.wingbits.url(DefaultWingbitsPort, false, "metrics"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
return wingbits.ParseMetrics(body)
|
||||
}
|
||||
|
||||
// getJSON fetches a URL and decodes its JSON body into a freshly allocated T.
|
||||
func getJSON[T any](ctx context.Context, c *Client, url string) (*T, error) {
|
||||
body, err := c.get(ctx, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer body.Close()
|
||||
out := new(T)
|
||||
if err := json.NewDecoder(body).Decode(out); err != nil {
|
||||
return nil, fmt.Errorf("wingbits: decoding %s: %w", url, err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// get performs a GET and returns the response body, which the caller must close.
|
||||
func (c *Client) get(ctx context.Context, url string) (io.ReadCloser, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wingbits: building request: %w", err)
|
||||
}
|
||||
req.Header.Set("User-Agent", c.userAgent)
|
||||
resp, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wingbits: GET %s: %w", url, err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
return nil, fmt.Errorf("wingbits: GET %s: unexpected status %s", url, resp.Status)
|
||||
}
|
||||
return resp.Body, nil
|
||||
}
|
||||
Reference in New Issue
Block a user