- Go 100%
| .forgejo/workflows | ||
| examples | ||
| sdk | ||
| .gitignore | ||
| .golangci.yml | ||
| go.mod | ||
| LICENSE | ||
| README.md | ||
serviceradar-sdk-go
ServiceRadar plugin SDK for Go (TinyGo/WASM).
Overview
This SDK lets you write ServiceRadar plugin checkers in Go without handling low-level WASM host calls. It handles:
- Config decoding from the host
- Result builder for
serviceradar.plugin_result.v1 - Logging bridge
- HTTP/TCP/UDP proxy wrappers
- Support for Websockets
- Event emission + alert promotion hints
Install
go get git.carverauto.dev/carverauto/serviceradar-sdk-go
Example
package main
import (
"context"
"fmt"
"git.carverauto.dev/carverauto/serviceradar-sdk-go/sdk"
)
type Config struct {
URL string `json:"url"`
WarnMS float64 `json:"warn_ms"`
CritMS float64 `json:"crit_ms"`
}
//export run_check
func run_check() {
_ = sdk.Execute(func() (*sdk.Result, error) {
var cfg Config
if err := sdk.LoadConfig(&cfg); err != nil {
return nil, err
}
resp, err := sdk.HTTP.GetContext(context.Background(), cfg.URL)
if err != nil {
return nil, fmt.Errorf("http request failed: %w", err)
}
latency := float64(resp.Duration.Milliseconds())
thresholds := sdk.Thresholds(cfg.WarnMS, cfg.CritMS)
return sdk.NewResult().
WithSummary(fmt.Sprintf("http %d in %.0fms", resp.Status, latency)).
WithThresholds(latency, thresholds.Warn, thresholds.Crit).
WithMetric("latency_ms", latency, "ms", thresholds).
WithStatCard("Latency", fmt.Sprintf("%.0fms", latency), "success"), nil
})
}
func main() {}
Examples
examples/http-check: HTTP latency check with thresholds and eventsexamples/tcp-check: TCP connectivity check with optional write/readexamples/udp-check: UDP send check with bytes-sent metricexamples/widgets-check: HTTP check demonstrating stat card, table, sparkline, and markdown widgets
API ergonomics
Execute and error handling
Execute accepts a function that returns (*Result, error) and itself returns an error:
err := sdk.Execute(func() (*sdk.Result, error) {
// ...
return sdk.Ok("ok"), nil
})
if err != nil {
// Optional: handle submit/serialize errors (logging is already done by the SDK)
}
If your function returns a non-nil error, the SDK auto-generates a critical result (or upgrades your result to critical) and records the error details in the payload. This keeps the happy path concise while still surfacing failures.
Defaults and zero-value behavior
Defaults are applied at the edge (right before serialization) so Serialize does not mutate the original object:
SchemaVersiondefaults to1Statusdefaults toUNKNOWNSummarydefaults to the status stringObservedAtdefaults to “now” in RFC3339Nano
This means var r sdk.Result is safe; serialization produces a valid payload without altering r.
Fluent builders
Result has both conventional setters (SetSummary, AddMetric, etc.) and fluent builders (WithSummary, WithMetric, etc.) so you can choose style:
return sdk.NewResult().
WithSummary("all good").
WithMetric("cpu", 10, "%", nil).
WithLabel("version", "1.2.3"), nil
Threshold helpers
Use ThresholdSpec for warning/critical thresholds and Thresholds(warn, crit) to build one without helper functions:
thresholds := sdk.Thresholds(50, 100)
res.WithMetric("latency_ms", 10, "ms", thresholds)
res.WithThresholds(10, thresholds.Warn, thresholds.Crit)
Context-aware I/O
Context variants exist for host I/O to match Go expectations:
- HTTP:
HTTP.DoContext,HTTP.GetContext,HTTP.PostContext - TCP:
TCPDialContext,(*TCPConn).ReadContext,(*TCPConn).WriteContext - UDP:
UDPSendToContext - WebSocket:
WebSocketDialContext,(*WebSocketConn).SendContext,(*WebSocketConn).RecvContext
These currently check ctx.Err() before the host call (TinyGo/Wasm is synchronous), but give you a stable API if cancellation support is added later.
WebSocket Support
The SDK provides WebSocket client capabilities for plugins that need to communicate with WebSocket servers:
// Dial a WebSocket endpoint
conn, err := sdk.WebSocketDialContext(ctx, "ws://localhost:8080/ws", 10*time.Second)
if err != nil {
return nil, fmt.Errorf("websocket dial failed: %w", err)
}
defer conn.Close()
// Send a message
if err := conn.SendContext(ctx, []byte(`{"method": "getInfo"}`), 10*time.Second); err != nil {
return nil, fmt.Errorf("websocket send failed: %w", err)
}
// Read response
buf := make([]byte, 4096)
n, err := conn.RecvContext(ctx, buf, 10*time.Second)
if err != nil {
return nil, fmt.Errorf("websocket recv failed: %w", err)
}
data := buf[:n]
WebSocket connections are mediated by the host runtime, which enforces:
- Domain allowlists: Only permitted domains can be connected to
- Port restrictions: Only allowed ports can be accessed
- Connection limits: Maximum concurrent connections per plugin
The plugin must have the following capabilities in its manifest:
websocket_connect: Permission to establish WebSocket connectionswebsocket_send: Permission to send messageswebsocket_recv: Permission to receive messageswebsocket_close: Permission to close connections
To include headers (for example Authorization) on the initial WebSocket handshake:
headers := map[string]string{
"Authorization": "Basic <base64-user-pass>",
}
conn, err := sdk.WebSocketConnectWithHeaders("wss://camera.local/vapix/ws-data-stream?sources=events", headers, 10*time.Second)
Config loading
LoadConfig is an alias of GetConfig for more idiomatic naming in user code:
if err := sdk.LoadConfig(&cfg); err != nil {
return nil, err
}
Policy input payload helpers (serviceradar.plugin_inputs.v1)
For policy-driven plugin assignments, decode and validate the typed input payload:
var payload sdk.PluginInputsPayload
if err := sdk.LoadConfig(&payload); err != nil {
return nil, err
}
if err := payload.Validate(); err != nil {
return nil, err
}
// Iterate all resolved items (devices/interfaces/etc.)
err := payload.EachItem(func(item sdk.PluginInputItem) error {
// item.Entity: "devices" | "interfaces" | ...
// item.Item: map with resolved fields (uid/ip/if_name/etc.)
return nil
})
if err != nil {
return nil, err
}
devices := payload.ItemsByEntity("devices")
_ = devices
Helpers also include:
sdk.ParsePluginInputsJSON([]byte)sdk.ParsePluginInputsMap(map[string]any)(*PluginInputsPayload).FlattenItems()(*PluginInputsPayload).ItemsByEntity(string)(*PluginInputsPayload).ItemsByName(string)
Build
# Requires TinyGo
cd examples/http-check
tinygo build -o plugin.wasm -target=wasi ./
Host ABI
The agent imports host functions from the env module:
get_configlogsubmit_resulthttp_requesttcp_connect/tcp_read/tcp_write/tcp_closeudp_sendtowebsocket_connect/websocket_send/websocket_recv/websocket_closecamera_media_open/camera_media_write/camera_media_heartbeat/camera_media_close
The SDK wraps these functions and exports alloc/dealloc for host memory access.
Event and Alert Hints
The SDK emits optional fields in the result payload to support event promotion:
events: list of OCSF Event Log Activity objectsalert_hint: boolean flag for immediate promotioncondition_id: string used for de-duplication and auto-clear logic
These fields are ignored safely by older control-plane builds.