Files
mesh-catalog/modules/nats/cmd/nats-tools/monitor.go
T
jochen f53fc0929b The bus's own tools: server, connections, subscriptions, streams, backlog, buckets, users, user_can
A Go bundle the runtime launches beside the nats module's server. It reads
the server's monitoring API and the composed user list — never a password
hash — and changes nothing. Reached directly when the endpoint is published,
through the container otherwise: its configuration binds monitoring to the
container's own loopback, so the published port answers nothing today.
2026-10-04 16:51:33 +02:00

68 lines
2.5 KiB
Go

package main
// Reaching the bus server's own monitoring API (the HTTP endpoints nats-server serves: varz, connz,
// subsz, jsz, healthz). Read-only by the server's own design: nothing here can change the bus.
//
// **Two ways in, the first that answers.** The module's configuration binds the endpoint inside its
// container; published on the machine's loopback, a host-side client reaches it directly — and where the
// binding is the container's own loopback (as the module shipped until this bundle), only a process inside
// the container can. So the endpoint is asked directly first, and through the container second; the day
// the binding is moved, the second way simply stops being used.
import (
"context"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"strings"
"time"
)
// Monitor fetches a monitoring path, e.g. "/varz".
type Monitor func(ctx context.Context, path string) ([]byte, error)
func env(key, fallback string) string {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}
return fallback
}
// LiveMonitor is the server as this machine reaches it.
func LiveMonitor() Monitor {
base := env("MESH_NATS_MONITOR", "http://127.0.0.1:8222")
container := env("MESH_NATS_CONTAINER", "mesh-broker-nats")
client := &http.Client{Timeout: 5 * time.Second}
return func(ctx context.Context, path string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, base+path, nil)
if err == nil {
if resp, err := client.Do(req); err == nil {
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return io.ReadAll(io.LimitReader(resp.Body, 64<<20))
}
}
}
// Through the container: its own loopback is where the endpoint listens.
out, err := exec.CommandContext(ctx, "docker", "exec", container, "wget", "-qO-", "http://127.0.0.1:8222"+path).Output()
if err != nil {
return nil, fmt.Errorf("the bus's monitoring endpoint answered neither at %s nor inside %s: %w", base, container, err)
}
return out, nil
}
}
// ReadUsers reads the composed user list the server includes: in the container, where it is mounted
// read-only; the machine's copy is readable by root alone.
func ReadUsers(ctx context.Context) ([]byte, error) {
container := env("MESH_NATS_CONTAINER", "mesh-broker-nats")
path := env("MESH_NATS_USERS_FILE", "/etc/nats/accounts.conf")
out, err := exec.CommandContext(ctx, "docker", "exec", container, "cat", path).Output()
if err != nil {
return nil, fmt.Errorf("the bus's user list could not be read inside %s: %w", container, err)
}
return out, nil
}