Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

87 lines
2.9 KiB
Go

package daemon
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
// RemoteHealth is the daemon-side view of a remote's GET /v1/health
// advertisement. It mirrors the server's HealthResponse JSON but keeps
// ReadOnly as a *bool so the consumer can distinguish "advertised
// read_only:false" (writable) from "field absent" (an older remote that
// does not advertise) — the latter is treated as read-only (fail-safe).
//
// It lives here, not as a shared type, because internal/daemon must not
// import internal/server (the import direction is server -> daemon).
type RemoteHealth struct {
Status string `json:"status"`
Indexed bool `json:"indexed"`
Nodes int `json:"nodes"`
Edges int `json:"edges"`
Version string `json:"version"`
UptimeSeconds float64 `json:"uptime_seconds"`
SchemaVersion int `json:"schema_version"`
APIVersion int `json:"api_version"`
ReadOnly *bool `json:"read_only"`
Capabilities []string `json:"capabilities,omitempty"`
}
// EffectiveReadOnly reports whether the remote must be treated as
// read-only. An unadvertised read_only (nil) is read-only by fail-safe
// default, so an older remote that predates the field is never assumed
// writable.
func (h RemoteHealth) EffectiveReadOnly() bool {
return h.ReadOnly == nil || *h.ReadOnly
}
// HasCapability reports whether the remote advertised a named federation
// capability (e.g. "subgraph", "events").
func (h RemoteHealth) HasCapability(name string) bool {
for _, c := range h.Capabilities {
if c == name {
return true
}
}
return false
}
// FetchHealth reads the remote's GET /v1/health under the caller's ctx
// (so a slow remote is bounded by the same per-remote deadline the read
// fan-out uses). The bearer token is sent per call, like every other
// remote hop.
func (c *ServerClient) FetchHealth(ctx context.Context) (RemoteHealth, error) {
var out RemoteHealth
u, err := url.JoinPath(c.BaseURL, "v1", "health")
if err != nil {
return out, fmt.Errorf("join health URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return out, fmt.Errorf("build health request: %w", err)
}
if tok := c.resolveAuthToken(); tok != "" {
req.Header.Set("Authorization", "Bearer "+tok)
}
req.Header.Set("Accept", "application/json")
resp, err := c.httpClient.Do(req)
if err != nil {
return out, fmt.Errorf("fetch health from %q: %w", c.Entry.Slug, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return out, fmt.Errorf("health from %q: status %d", c.Entry.Slug, resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return out, fmt.Errorf("read health response: %w", err)
}
if err := json.Unmarshal(body, &out); err != nil {
return out, fmt.Errorf("decode health from %q: %w", c.Entry.Slug, err)
}
return out, nil
}