coder/codersdk/healthsdk/healthsdk.go

236 lines
7.8 KiB
Go

package healthsdk
import (
"context"
"encoding/json"
"net/http"
"time"
"golang.org/x/xerrors"
"tailscale.com/derp"
"tailscale.com/net/netcheck"
"tailscale.com/tailcfg"
"github.com/coder/coder/v2/coderd/healthcheck/health"
"github.com/coder/coder/v2/codersdk"
)
// @typescript-ignore HealthClient
type HealthClient struct {
client *codersdk.Client
}
func New(c *codersdk.Client) *HealthClient {
return &HealthClient{client: c}
}
type HealthSection string
// If you add another const below, make sure to add it to HealthSections!
const (
HealthSectionDERP HealthSection = "DERP"
HealthSectionAccessURL HealthSection = "AccessURL"
HealthSectionWebsocket HealthSection = "Websocket"
HealthSectionDatabase HealthSection = "Database"
HealthSectionWorkspaceProxy HealthSection = "WorkspaceProxy"
HealthSectionProvisionerDaemons HealthSection = "ProvisionerDaemons"
)
var HealthSections = []HealthSection{
HealthSectionDERP,
HealthSectionAccessURL,
HealthSectionWebsocket,
HealthSectionDatabase,
HealthSectionWorkspaceProxy,
HealthSectionProvisionerDaemons,
}
type HealthSettings struct {
DismissedHealthchecks []HealthSection `json:"dismissed_healthchecks"`
}
type UpdateHealthSettings struct {
DismissedHealthchecks []HealthSection `json:"dismissed_healthchecks"`
}
func (c *HealthClient) DebugHealth(ctx context.Context) (HealthcheckReport, error) {
res, err := c.client.Request(ctx, http.MethodGet, "/api/v2/debug/health", nil)
if err != nil {
return HealthcheckReport{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return HealthcheckReport{}, codersdk.ReadBodyAsError(res)
}
var rpt HealthcheckReport
return rpt, json.NewDecoder(res.Body).Decode(&rpt)
}
func (c *HealthClient) HealthSettings(ctx context.Context) (HealthSettings, error) {
res, err := c.client.Request(ctx, http.MethodGet, "/api/v2/debug/health/settings", nil)
if err != nil {
return HealthSettings{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return HealthSettings{}, codersdk.ReadBodyAsError(res)
}
var settings HealthSettings
return settings, json.NewDecoder(res.Body).Decode(&settings)
}
func (c *HealthClient) PutHealthSettings(ctx context.Context, settings HealthSettings) error {
res, err := c.client.Request(ctx, http.MethodPut, "/api/v2/debug/health/settings", settings)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode == http.StatusNoContent {
return xerrors.New("health settings not modified")
}
if res.StatusCode != http.StatusOK {
return codersdk.ReadBodyAsError(res)
}
return nil
}
type HealthcheckReport struct {
// Time is the time the report was generated at.
Time time.Time `json:"time" format:"date-time"`
// Healthy is true if the report returns no errors.
// Deprecated: use `Severity` instead
Healthy bool `json:"healthy"`
// Severity indicates the status of Coder health.
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
// FailingSections is a list of sections that have failed their healthcheck.
FailingSections []HealthSection `json:"failing_sections"`
DERP DERPHealthReport `json:"derp"`
AccessURL AccessURLReport `json:"access_url"`
Websocket WebsocketReport `json:"websocket"`
Database DatabaseReport `json:"database"`
WorkspaceProxy WorkspaceProxyReport `json:"workspace_proxy"`
ProvisionerDaemons ProvisionerDaemonsReport `json:"provisioner_daemons"`
// The Coder version of the server that the report was generated on.
CoderVersion string `json:"coder_version"`
}
type AccessURLReport struct {
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
Healthy bool `json:"healthy"`
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
Warnings []health.Message `json:"warnings"`
Dismissed bool `json:"dismissed"`
AccessURL string `json:"access_url"`
Reachable bool `json:"reachable"`
StatusCode int `json:"status_code"`
HealthzResponse string `json:"healthz_response"`
Error *string `json:"error"`
}
type DERPHealthReport struct {
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
Healthy bool `json:"healthy"`
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
Warnings []health.Message `json:"warnings"`
Dismissed bool `json:"dismissed"`
Regions map[int]*DERPRegionReport `json:"regions"`
Netcheck *netcheck.Report `json:"netcheck"`
NetcheckErr *string `json:"netcheck_err"`
NetcheckLogs []string `json:"netcheck_logs"`
Error *string `json:"error"`
}
type DERPRegionReport struct {
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
Healthy bool `json:"healthy"`
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
Warnings []health.Message `json:"warnings"`
Region *tailcfg.DERPRegion `json:"region"`
NodeReports []*DERPNodeReport `json:"node_reports"`
Error *string `json:"error"`
}
type DERPNodeReport struct {
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
Healthy bool `json:"healthy"`
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
Warnings []health.Message `json:"warnings"`
Node *tailcfg.DERPNode `json:"node"`
ServerInfo derp.ServerInfoMessage `json:"node_info"`
CanExchangeMessages bool `json:"can_exchange_messages"`
RoundTripPing string `json:"round_trip_ping"`
RoundTripPingMs int `json:"round_trip_ping_ms"`
UsesWebsocket bool `json:"uses_websocket"`
ClientLogs [][]string `json:"client_logs"`
ClientErrs [][]string `json:"client_errs"`
Error *string `json:"error"`
STUN STUNReport `json:"stun"`
}
type STUNReport struct {
Enabled bool
CanSTUN bool
Error *string
}
type DatabaseReport struct {
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
Healthy bool `json:"healthy"`
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
Warnings []health.Message `json:"warnings"`
Dismissed bool `json:"dismissed"`
Reachable bool `json:"reachable"`
Latency string `json:"latency"`
LatencyMS int64 `json:"latency_ms"`
ThresholdMS int64 `json:"threshold_ms"`
Error *string `json:"error"`
}
type ProvisionerDaemonsReport struct {
Severity health.Severity `json:"severity"`
Warnings []health.Message `json:"warnings"`
Dismissed bool `json:"dismissed"`
Error *string `json:"error"`
Items []ProvisionerDaemonsReportItem `json:"items"`
}
type ProvisionerDaemonsReportItem struct {
codersdk.ProvisionerDaemon `json:"provisioner_daemon"`
Warnings []health.Message `json:"warnings"`
}
type WebsocketReport struct {
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
Healthy bool `json:"healthy"`
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
Warnings []string `json:"warnings"`
Dismissed bool `json:"dismissed"`
Body string `json:"body"`
Code int `json:"code"`
Error *string `json:"error"`
}
type WorkspaceProxyReport struct {
Healthy bool `json:"healthy"`
Severity health.Severity `json:"severity"`
Warnings []health.Message `json:"warnings"`
Dismissed bool `json:"dismissed"`
Error *string `json:"error"`
WorkspaceProxies codersdk.RegionsResponse[codersdk.WorkspaceProxy] `json:"workspace_proxies"`
}