From b8a143566bed146fe25a5dac18d458f40a2ee746 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 27 Jun 2023 14:13:54 -0500 Subject: [PATCH] fix: use `*string` instead of `error` in healthcheck response (#8234) --- coderd/apidoc/docs.go | 40 ++++++++--- coderd/apidoc/swagger.json | 40 ++++++++--- coderd/healthcheck/accessurl.go | 24 ++++--- coderd/healthcheck/accessurl_test.go | 7 +- coderd/healthcheck/database.go | 4 +- coderd/healthcheck/database_test.go | 8 ++- coderd/healthcheck/derp.go | 24 +++---- coderd/healthcheck/healthcheck.go | 28 ++++++-- coderd/healthcheck/healthcheck_test.go | 1 + coderd/healthcheck/websocket.go | 14 ++-- coderd/healthcheck/websocket_test.go | 4 +- docs/api/debug.md | 24 +++---- docs/api/schemas.md | 91 ++++++++++++++------------ 13 files changed, 195 insertions(+), 114 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 872fd02287..c2e45d069c 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -9953,7 +9953,12 @@ const docTemplate = `{ "healthcheck.AccessURLReport": { "type": "object", "properties": { - "error": {}, + "access_url": { + "type": "string" + }, + "error": { + "type": "string" + }, "healthy": { "type": "boolean" }, @@ -9978,7 +9983,9 @@ const docTemplate = `{ "type": "array", "items": { "type": "array", - "items": {} + "items": { + "type": "string" + } } }, "client_logs": { @@ -9990,7 +9997,9 @@ const docTemplate = `{ } } }, - "error": {}, + "error": { + "type": "string" + }, "healthy": { "type": "boolean" }, @@ -10014,7 +10023,9 @@ const docTemplate = `{ "healthcheck.DERPRegionReport": { "type": "object", "properties": { - "error": {}, + "error": { + "type": "string" + }, "healthy": { "type": "boolean" }, @@ -10032,14 +10043,18 @@ const docTemplate = `{ "healthcheck.DERPReport": { "type": "object", "properties": { - "error": {}, + "error": { + "type": "string" + }, "healthy": { "type": "boolean" }, "netcheck": { "$ref": "#/definitions/netcheck.Report" }, - "netcheck_err": {}, + "netcheck_err": { + "type": "string" + }, "netcheck_logs": { "type": "array", "items": { @@ -10069,7 +10084,9 @@ const docTemplate = `{ "healthcheck.DatabaseReport": { "type": "object", "properties": { - "error": {}, + "error": { + "type": "string" + }, "healthy": { "type": "boolean" }, @@ -10087,6 +10104,10 @@ const docTemplate = `{ "access_url": { "$ref": "#/definitions/healthcheck.AccessURLReport" }, + "coder_version": { + "description": "The Coder version of the server that the report was generated on.", + "type": "string" + }, "database": { "$ref": "#/definitions/healthcheck.DatabaseReport" }, @@ -10094,6 +10115,7 @@ const docTemplate = `{ "$ref": "#/definitions/healthcheck.DERPReport" }, "failing_sections": { + "description": "FailingSections is a list of sections that have failed their healthcheck.", "type": "array", "items": { "type": "string" @@ -10115,7 +10137,9 @@ const docTemplate = `{ "healthcheck.WebsocketReport": { "type": "object", "properties": { - "error": {}, + "error": { + "type": "string" + }, "healthy": { "type": "boolean" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 56db90e9f2..4f76a78318 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -9008,7 +9008,12 @@ "healthcheck.AccessURLReport": { "type": "object", "properties": { - "error": {}, + "access_url": { + "type": "string" + }, + "error": { + "type": "string" + }, "healthy": { "type": "boolean" }, @@ -9033,7 +9038,9 @@ "type": "array", "items": { "type": "array", - "items": {} + "items": { + "type": "string" + } } }, "client_logs": { @@ -9045,7 +9052,9 @@ } } }, - "error": {}, + "error": { + "type": "string" + }, "healthy": { "type": "boolean" }, @@ -9069,7 +9078,9 @@ "healthcheck.DERPRegionReport": { "type": "object", "properties": { - "error": {}, + "error": { + "type": "string" + }, "healthy": { "type": "boolean" }, @@ -9087,14 +9098,18 @@ "healthcheck.DERPReport": { "type": "object", "properties": { - "error": {}, + "error": { + "type": "string" + }, "healthy": { "type": "boolean" }, "netcheck": { "$ref": "#/definitions/netcheck.Report" }, - "netcheck_err": {}, + "netcheck_err": { + "type": "string" + }, "netcheck_logs": { "type": "array", "items": { @@ -9124,7 +9139,9 @@ "healthcheck.DatabaseReport": { "type": "object", "properties": { - "error": {}, + "error": { + "type": "string" + }, "healthy": { "type": "boolean" }, @@ -9142,6 +9159,10 @@ "access_url": { "$ref": "#/definitions/healthcheck.AccessURLReport" }, + "coder_version": { + "description": "The Coder version of the server that the report was generated on.", + "type": "string" + }, "database": { "$ref": "#/definitions/healthcheck.DatabaseReport" }, @@ -9149,6 +9170,7 @@ "$ref": "#/definitions/healthcheck.DERPReport" }, "failing_sections": { + "description": "FailingSections is a list of sections that have failed their healthcheck.", "type": "array", "items": { "type": "string" @@ -9170,7 +9192,9 @@ "healthcheck.WebsocketReport": { "type": "object", "properties": { - "error": {}, + "error": { + "type": "string" + }, "healthy": { "type": "boolean" }, diff --git a/coderd/healthcheck/accessurl.go b/coderd/healthcheck/accessurl.go index 0edc827a62..b25e956b7d 100644 --- a/coderd/healthcheck/accessurl.go +++ b/coderd/healthcheck/accessurl.go @@ -8,14 +8,17 @@ import ( "time" "golang.org/x/xerrors" + + "github.com/coder/coder/coderd/util/ptr" ) type AccessURLReport struct { - Healthy bool `json:"healthy"` - Reachable bool `json:"reachable"` - StatusCode int `json:"status_code"` - HealthzResponse string `json:"healthz_response"` - Error error `json:"error"` + AccessURL string `json:"access_url"` + Healthy bool `json:"healthy"` + Reachable bool `json:"reachable"` + StatusCode int `json:"status_code"` + HealthzResponse string `json:"healthz_response"` + Error *string `json:"error"` } type AccessURLReportOptions struct { @@ -28,9 +31,10 @@ func (r *AccessURLReport) Run(ctx context.Context, opts *AccessURLReportOptions) defer cancel() if opts.AccessURL == nil { - r.Error = xerrors.New("access URL is nil") + r.Error = ptr.Ref("access URL is nil") return } + r.AccessURL = opts.AccessURL.String() if opts.Client == nil { opts.Client = http.DefaultClient @@ -38,26 +42,26 @@ func (r *AccessURLReport) Run(ctx context.Context, opts *AccessURLReportOptions) accessURL, err := opts.AccessURL.Parse("/healthz") if err != nil { - r.Error = xerrors.Errorf("parse healthz endpoint: %w", err) + r.Error = convertError(xerrors.Errorf("parse healthz endpoint: %w", err)) return } req, err := http.NewRequestWithContext(ctx, "GET", accessURL.String(), nil) if err != nil { - r.Error = xerrors.Errorf("create healthz request: %w", err) + r.Error = convertError(xerrors.Errorf("create healthz request: %w", err)) return } res, err := opts.Client.Do(req) if err != nil { - r.Error = xerrors.Errorf("get healthz endpoint: %w", err) + r.Error = convertError(xerrors.Errorf("get healthz endpoint: %w", err)) return } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { - r.Error = xerrors.Errorf("read healthz response: %w", err) + r.Error = convertError(xerrors.Errorf("read healthz response: %w", err)) return } diff --git a/coderd/healthcheck/accessurl_test.go b/coderd/healthcheck/accessurl_test.go index 95545c2528..6097d6cb50 100644 --- a/coderd/healthcheck/accessurl_test.go +++ b/coderd/healthcheck/accessurl_test.go @@ -36,7 +36,7 @@ func TestAccessURL(t *testing.T) { assert.True(t, report.Reachable) assert.Equal(t, http.StatusOK, report.StatusCode) assert.Equal(t, "OK", report.HealthzResponse) - assert.NoError(t, report.Error) + assert.Nil(t, report.Error) }) t.Run("404", func(t *testing.T) { @@ -66,7 +66,7 @@ func TestAccessURL(t *testing.T) { assert.True(t, report.Reachable) assert.Equal(t, http.StatusNotFound, report.StatusCode) assert.Equal(t, string(resp), report.HealthzResponse) - assert.NoError(t, report.Error) + assert.Nil(t, report.Error) }) t.Run("ClientErr", func(t *testing.T) { @@ -102,7 +102,8 @@ func TestAccessURL(t *testing.T) { assert.False(t, report.Reachable) assert.Equal(t, 0, report.StatusCode) assert.Equal(t, "", report.HealthzResponse) - assert.ErrorIs(t, report.Error, expErr) + require.NotNil(t, report.Error) + assert.Contains(t, *report.Error, expErr.Error()) }) } diff --git a/coderd/healthcheck/database.go b/coderd/healthcheck/database.go index eb37744d02..291b9361b8 100644 --- a/coderd/healthcheck/database.go +++ b/coderd/healthcheck/database.go @@ -14,7 +14,7 @@ type DatabaseReport struct { Healthy bool `json:"healthy"` Reachable bool `json:"reachable"` Latency time.Duration `json:"latency"` - Error error `json:"error"` + Error *string `json:"error"` } type DatabaseReportOptions struct { @@ -31,7 +31,7 @@ func (r *DatabaseReport) Run(ctx context.Context, opts *DatabaseReportOptions) { for i := 0; i < pingCount; i++ { pong, err := opts.DB.Ping(ctx) if err != nil { - r.Error = xerrors.Errorf("ping: %w", err) + r.Error = convertError(xerrors.Errorf("ping: %w", err)) return } pings = append(pings, pong) diff --git a/coderd/healthcheck/database_test.go b/coderd/healthcheck/database_test.go index 5d5dc3035d..bf9f644713 100644 --- a/coderd/healthcheck/database_test.go +++ b/coderd/healthcheck/database_test.go @@ -7,6 +7,7 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/xerrors" "github.com/coder/coder/coderd/database/dbmock" @@ -35,7 +36,7 @@ func TestDatabase(t *testing.T) { assert.True(t, report.Healthy) assert.True(t, report.Reachable) assert.Equal(t, ping, report.Latency) - assert.NoError(t, report.Error) + assert.Nil(t, report.Error) }) t.Run("Error", func(t *testing.T) { @@ -56,7 +57,8 @@ func TestDatabase(t *testing.T) { assert.False(t, report.Healthy) assert.False(t, report.Reachable) assert.Zero(t, report.Latency) - assert.ErrorIs(t, report.Error, err) + require.NotNil(t, report.Error) + assert.Contains(t, *report.Error, err.Error()) }) t.Run("Median", func(t *testing.T) { @@ -80,6 +82,6 @@ func TestDatabase(t *testing.T) { assert.True(t, report.Healthy) assert.True(t, report.Reachable) assert.Equal(t, time.Millisecond, report.Latency) - assert.NoError(t, report.Error) + assert.Nil(t, report.Error) }) } diff --git a/coderd/healthcheck/derp.go b/coderd/healthcheck/derp.go index 7a5e8bbf55..9472fcd98d 100644 --- a/coderd/healthcheck/derp.go +++ b/coderd/healthcheck/derp.go @@ -30,10 +30,10 @@ type DERPReport struct { Regions map[int]*DERPRegionReport `json:"regions"` Netcheck *netcheck.Report `json:"netcheck"` - NetcheckErr error `json:"netcheck_err"` + NetcheckErr *string `json:"netcheck_err"` NetcheckLogs []string `json:"netcheck_logs"` - Error error `json:"error"` + Error *string `json:"error"` } type DERPRegionReport struct { @@ -42,7 +42,7 @@ type DERPRegionReport struct { Region *tailcfg.DERPRegion `json:"region"` NodeReports []*DERPNodeReport `json:"node_reports"` - Error error `json:"error"` + Error *string `json:"error"` } type DERPNodeReport struct { mu sync.Mutex @@ -56,8 +56,8 @@ type DERPNodeReport struct { RoundTripPing time.Duration `json:"round_trip_ping"` UsesWebsocket bool `json:"uses_websocket"` ClientLogs [][]string `json:"client_logs"` - ClientErrs [][]error `json:"client_errs"` - Error error `json:"error"` + ClientErrs [][]string `json:"client_errs"` + Error *string `json:"error"` STUN DERPStunReport `json:"stun"` } @@ -91,7 +91,7 @@ func (r *DERPReport) Run(ctx context.Context, opts *DERPReportOptions) { defer wg.Done() defer func() { if err := recover(); err != nil { - regionReport.Error = xerrors.Errorf("%v", err) + regionReport.Error = ptr.Ref(fmt.Sprint(err)) } }() @@ -115,7 +115,9 @@ func (r *DERPReport) Run(ctx context.Context, opts *DERPReportOptions) { PortMapper: portmapper.NewClient(tslogger.WithPrefix(ncLogf, "portmap: "), nil), Logf: tslogger.WithPrefix(ncLogf, "netcheck: "), } - r.Netcheck, r.NetcheckErr = nc.GetReport(ctx, opts.DERPMap) + ncReport, netcheckErr := nc.GetReport(ctx, opts.DERPMap) + r.Netcheck = ncReport + r.NetcheckErr = convertError(netcheckErr) wg.Wait() } @@ -140,7 +142,7 @@ func (r *DERPRegionReport) Run(ctx context.Context) { defer wg.Done() defer func() { if err := recover(); err != nil { - nodeReport.Error = xerrors.Errorf("%v", err) + nodeReport.Error = ptr.Ref(fmt.Sprint(err)) } }() @@ -179,7 +181,7 @@ func (r *DERPNodeReport) Run(ctx context.Context) { defer cancel() r.ClientLogs = [][]string{} - r.ClientErrs = [][]error{} + r.ClientErrs = [][]string{} wg := &sync.WaitGroup{} @@ -376,7 +378,7 @@ func (r *DERPNodeReport) stunAddr(ctx context.Context) (string, int, error) { func (r *DERPNodeReport) writeClientErr(clientID int, err error) { r.mu.Lock() - r.ClientErrs[clientID] = append(r.ClientErrs[clientID], err) + r.ClientErrs[clientID] = append(r.ClientErrs[clientID], err.Error()) r.mu.Unlock() } @@ -385,7 +387,7 @@ func (r *DERPNodeReport) derpClient(ctx context.Context, derpURL *url.URL) (*der id := r.clientCounter r.clientCounter++ r.ClientLogs = append(r.ClientLogs, []string{}) - r.ClientErrs = append(r.ClientErrs, []error{}) + r.ClientErrs = append(r.ClientErrs, []string{}) r.mu.Unlock() client, err := derphttp.NewClient(key.NewNode(), derpURL.String(), func(format string, args ...any) { diff --git a/coderd/healthcheck/healthcheck.go b/coderd/healthcheck/healthcheck.go index 951e982fd3..dea09600ca 100644 --- a/coderd/healthcheck/healthcheck.go +++ b/coderd/healthcheck/healthcheck.go @@ -2,15 +2,17 @@ package healthcheck import ( "context" + "fmt" "net/http" "net/url" "sync" "time" - "golang.org/x/xerrors" "tailscale.com/tailcfg" + "github.com/coder/coder/buildinfo" "github.com/coder/coder/coderd/database" + "github.com/coder/coder/coderd/util/ptr" ) const ( @@ -31,13 +33,17 @@ type Report struct { // Time is the time the report was generated at. Time time.Time `json:"time"` // Healthy is true if the report returns no errors. - Healthy bool `json:"healthy"` + Healthy bool `json:"healthy"` + // FailingSections is a list of sections that have failed their healthcheck. FailingSections []string `json:"failing_sections"` DERP DERPReport `json:"derp"` AccessURL AccessURLReport `json:"access_url"` Websocket WebsocketReport `json:"websocket"` Database DatabaseReport `json:"database"` + + // The Coder version of the server that the report was generated on. + CoderVersion string `json:"coder_version"` } type ReportOptions struct { @@ -88,7 +94,7 @@ func Run(ctx context.Context, opts *ReportOptions) *Report { defer wg.Done() defer func() { if err := recover(); err != nil { - report.DERP.Error = xerrors.Errorf("%v", err) + report.DERP.Error = ptr.Ref(fmt.Sprint(err)) } }() @@ -102,7 +108,7 @@ func Run(ctx context.Context, opts *ReportOptions) *Report { defer wg.Done() defer func() { if err := recover(); err != nil { - report.AccessURL.Error = xerrors.Errorf("%v", err) + report.AccessURL.Error = ptr.Ref(fmt.Sprint(err)) } }() @@ -117,7 +123,7 @@ func Run(ctx context.Context, opts *ReportOptions) *Report { defer wg.Done() defer func() { if err := recover(); err != nil { - report.Websocket.Error = xerrors.Errorf("%v", err) + report.Websocket.Error = ptr.Ref(fmt.Sprint(err)) } }() @@ -132,7 +138,7 @@ func Run(ctx context.Context, opts *ReportOptions) *Report { defer wg.Done() defer func() { if err := recover(); err != nil { - report.Database.Error = xerrors.Errorf("%v", err) + report.Database.Error = ptr.Ref(fmt.Sprint(err)) } }() @@ -141,7 +147,9 @@ func Run(ctx context.Context, opts *ReportOptions) *Report { }) }() + report.CoderVersion = buildinfo.Version() wg.Wait() + report.Time = time.Now() if !report.DERP.Healthy { report.FailingSections = append(report.FailingSections, SectionDERP) @@ -159,3 +167,11 @@ func Run(ctx context.Context, opts *ReportOptions) *Report { report.Healthy = len(report.FailingSections) == 0 return &report } + +func convertError(err error) *string { + if err != nil { + return ptr.Ref(err.Error()) + } + + return nil +} diff --git a/coderd/healthcheck/healthcheck_test.go b/coderd/healthcheck/healthcheck_test.go index 60dac7deb9..26be9021ea 100644 --- a/coderd/healthcheck/healthcheck_test.go +++ b/coderd/healthcheck/healthcheck_test.go @@ -155,6 +155,7 @@ func TestHealthcheck(t *testing.T) { assert.Equal(t, c.checker.AccessURLReport.Healthy, report.AccessURL.Healthy) assert.Equal(t, c.checker.WebsocketReport.Healthy, report.Websocket.Healthy) assert.NotZero(t, report.Time) + assert.NotZero(t, report.CoderVersion) }) } } diff --git a/coderd/healthcheck/websocket.go b/coderd/healthcheck/websocket.go index e69d6339ff..f2ab754f45 100644 --- a/coderd/healthcheck/websocket.go +++ b/coderd/healthcheck/websocket.go @@ -23,7 +23,7 @@ type WebsocketReportOptions struct { type WebsocketReport struct { Healthy bool `json:"healthy"` Response WebsocketResponse `json:"response"` - Error error `json:"error"` + Error *string `json:"error"` } type WebsocketResponse struct { @@ -37,7 +37,7 @@ func (r *WebsocketReport) Run(ctx context.Context, opts *WebsocketReportOptions) u, err := opts.AccessURL.Parse("/api/v2/debug/ws") if err != nil { - r.Error = xerrors.Errorf("parse access url: %w", err) + r.Error = convertError(xerrors.Errorf("parse access url: %w", err)) return } if u.Scheme == "https" { @@ -66,7 +66,7 @@ func (r *WebsocketReport) Run(ctx context.Context, opts *WebsocketReportOptions) } } if err != nil { - r.Error = xerrors.Errorf("websocket dial: %w", err) + r.Error = convertError(xerrors.Errorf("websocket dial: %w", err)) return } defer c.Close(websocket.StatusGoingAway, "goodbye") @@ -75,23 +75,23 @@ func (r *WebsocketReport) Run(ctx context.Context, opts *WebsocketReportOptions) msg := strconv.Itoa(i) err := c.Write(ctx, websocket.MessageText, []byte(msg)) if err != nil { - r.Error = xerrors.Errorf("write message: %w", err) + r.Error = convertError(xerrors.Errorf("write message: %w", err)) return } ty, got, err := c.Read(ctx) if err != nil { - r.Error = xerrors.Errorf("read message: %w", err) + r.Error = convertError(xerrors.Errorf("read message: %w", err)) return } if ty != websocket.MessageText { - r.Error = xerrors.Errorf("received incorrect message type: %v", ty) + r.Error = convertError(xerrors.Errorf("received incorrect message type: %v", ty)) return } if string(got) != msg { - r.Error = xerrors.Errorf("received incorrect message: wanted %q, got %q", msg, string(got)) + r.Error = convertError(xerrors.Errorf("received incorrect message: wanted %q, got %q", msg, string(got))) return } } diff --git a/coderd/healthcheck/websocket_test.go b/coderd/healthcheck/websocket_test.go index 6b5f17fc24..45f8a4ae5c 100644 --- a/coderd/healthcheck/websocket_test.go +++ b/coderd/healthcheck/websocket_test.go @@ -37,7 +37,7 @@ func TestWebsocket(t *testing.T) { APIKey: "test", }) - require.NoError(t, wsReport.Error) + require.Nil(t, wsReport.Error) }) t.Run("Error", func(t *testing.T) { @@ -62,7 +62,7 @@ func TestWebsocket(t *testing.T) { APIKey: "test", }) - require.Error(t, wsReport.Error) + require.NotNil(t, wsReport.Error) assert.Equal(t, wsReport.Response.Body, "test error") assert.Equal(t, wsReport.Response.Code, http.StatusBadRequest) }) diff --git a/docs/api/debug.md b/docs/api/debug.md index d44f40d557..cd68322a67 100644 --- a/docs/api/debug.md +++ b/docs/api/debug.md @@ -40,20 +40,22 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \ ```json { "access_url": { - "error": null, + "access_url": "string", + "error": "string", "healthy": true, "healthz_response": "string", "reachable": true, "status_code": 0 }, + "coder_version": "string", "database": { - "error": null, + "error": "string", "healthy": true, "latency": 0, "reachable": true }, "derp": { - "error": null, + "error": "string", "healthy": true, "netcheck": { "captivePortal": "string", @@ -85,18 +87,18 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \ "udp": true, "upnP": "string" }, - "netcheck_err": null, + "netcheck_err": "string", "netcheck_logs": ["string"], "regions": { "property1": { - "error": null, + "error": "string", "healthy": true, "node_reports": [ { "can_exchange_messages": true, - "client_errs": [[null]], + "client_errs": [["string"]], "client_logs": [["string"]], - "error": null, + "error": "string", "healthy": true, "node": { "certName": "string", @@ -150,14 +152,14 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \ } }, "property2": { - "error": null, + "error": "string", "healthy": true, "node_reports": [ { "can_exchange_messages": true, - "client_errs": [[null]], + "client_errs": [["string"]], "client_logs": [["string"]], - "error": null, + "error": "string", "healthy": true, "node": { "certName": "string", @@ -216,7 +218,7 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \ "healthy": true, "time": "string", "websocket": { - "error": null, + "error": "string", "healthy": true, "response": { "body": "string", diff --git a/docs/api/schemas.md b/docs/api/schemas.md index f332d03968..8dcac8e58a 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -5665,7 +5665,8 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { - "error": null, + "access_url": "string", + "error": "string", "healthy": true, "healthz_response": "string", "reachable": true, @@ -5677,7 +5678,8 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | Name | Type | Required | Restrictions | Description | | ------------------ | ------- | -------- | ------------ | ----------- | -| `error` | any | false | | | +| `access_url` | string | false | | | +| `error` | string | false | | | | `healthy` | boolean | false | | | | `healthz_response` | string | false | | | | `reachable` | boolean | false | | | @@ -5688,9 +5690,9 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { "can_exchange_messages": true, - "client_errs": [[null]], + "client_errs": [["string"]], "client_logs": [["string"]], - "error": null, + "error": "string", "healthy": true, "node": { "certName": "string", @@ -5727,7 +5729,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `can_exchange_messages` | boolean | false | | | | `client_errs` | array of array | false | | | | `client_logs` | array of array | false | | | -| `error` | any | false | | | +| `error` | string | false | | | | `healthy` | boolean | false | | | | `node` | [tailcfg.DERPNode](#tailcfgderpnode) | false | | | | `node_info` | [derp.ServerInfoMessage](#derpserverinfomessage) | false | | | @@ -5739,14 +5741,14 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { - "error": null, + "error": "string", "healthy": true, "node_reports": [ { "can_exchange_messages": true, - "client_errs": [[null]], + "client_errs": [["string"]], "client_logs": [["string"]], - "error": null, + "error": "string", "healthy": true, "node": { "certName": "string", @@ -5805,7 +5807,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | Name | Type | Required | Restrictions | Description | | -------------- | ----------------------------------------------------------------- | -------- | ------------ | ----------- | -| `error` | any | false | | | +| `error` | string | false | | | | `healthy` | boolean | false | | | | `node_reports` | array of [healthcheck.DERPNodeReport](#healthcheckderpnodereport) | false | | | | `region` | [tailcfg.DERPRegion](#tailcfgderpregion) | false | | | @@ -5814,7 +5816,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { - "error": null, + "error": "string", "healthy": true, "netcheck": { "captivePortal": "string", @@ -5846,18 +5848,18 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "udp": true, "upnP": "string" }, - "netcheck_err": null, + "netcheck_err": "string", "netcheck_logs": ["string"], "regions": { "property1": { - "error": null, + "error": "string", "healthy": true, "node_reports": [ { "can_exchange_messages": true, - "client_errs": [[null]], + "client_errs": [["string"]], "client_logs": [["string"]], - "error": null, + "error": "string", "healthy": true, "node": { "certName": "string", @@ -5911,14 +5913,14 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in } }, "property2": { - "error": null, + "error": "string", "healthy": true, "node_reports": [ { "can_exchange_messages": true, - "client_errs": [[null]], + "client_errs": [["string"]], "client_logs": [["string"]], - "error": null, + "error": "string", "healthy": true, "node": { "certName": "string", @@ -5979,10 +5981,10 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | Name | Type | Required | Restrictions | Description | | ------------------ | ------------------------------------------------------------ | -------- | ------------ | ----------- | -| `error` | any | false | | | +| `error` | string | false | | | | `healthy` | boolean | false | | | | `netcheck` | [netcheck.Report](#netcheckreport) | false | | | -| `netcheck_err` | any | false | | | +| `netcheck_err` | string | false | | | | `netcheck_logs` | array of string | false | | | | `regions` | object | false | | | | ยป `[any property]` | [healthcheck.DERPRegionReport](#healthcheckderpregionreport) | false | | | @@ -6009,7 +6011,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { - "error": null, + "error": "string", "healthy": true, "latency": 0, "reachable": true @@ -6020,7 +6022,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | Name | Type | Required | Restrictions | Description | | ----------- | ------- | -------- | ------------ | ----------- | -| `error` | any | false | | | +| `error` | string | false | | | | `healthy` | boolean | false | | | | `latency` | integer | false | | | | `reachable` | boolean | false | | | @@ -6030,20 +6032,22 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ```json { "access_url": { - "error": null, + "access_url": "string", + "error": "string", "healthy": true, "healthz_response": "string", "reachable": true, "status_code": 0 }, + "coder_version": "string", "database": { - "error": null, + "error": "string", "healthy": true, "latency": 0, "reachable": true }, "derp": { - "error": null, + "error": "string", "healthy": true, "netcheck": { "captivePortal": "string", @@ -6075,18 +6079,18 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "udp": true, "upnP": "string" }, - "netcheck_err": null, + "netcheck_err": "string", "netcheck_logs": ["string"], "regions": { "property1": { - "error": null, + "error": "string", "healthy": true, "node_reports": [ { "can_exchange_messages": true, - "client_errs": [[null]], + "client_errs": [["string"]], "client_logs": [["string"]], - "error": null, + "error": "string", "healthy": true, "node": { "certName": "string", @@ -6140,14 +6144,14 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in } }, "property2": { - "error": null, + "error": "string", "healthy": true, "node_reports": [ { "can_exchange_messages": true, - "client_errs": [[null]], + "client_errs": [["string"]], "client_logs": [["string"]], - "error": null, + "error": "string", "healthy": true, "node": { "certName": "string", @@ -6206,7 +6210,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "healthy": true, "time": "string", "websocket": { - "error": null, + "error": "string", "healthy": true, "response": { "body": "string", @@ -6218,21 +6222,22 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ---------------------------------------------------------- | -------- | ------------ | ------------------------------------------------ | -| `access_url` | [healthcheck.AccessURLReport](#healthcheckaccessurlreport) | false | | | -| `database` | [healthcheck.DatabaseReport](#healthcheckdatabasereport) | false | | | -| `derp` | [healthcheck.DERPReport](#healthcheckderpreport) | false | | | -| `failing_sections` | array of string | false | | | -| `healthy` | boolean | false | | Healthy is true if the report returns no errors. | -| `time` | string | false | | Time is the time the report was generated at. | -| `websocket` | [healthcheck.WebsocketReport](#healthcheckwebsocketreport) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ---------------------------------------------------------- | -------- | ------------ | -------------------------------------------------------------------------- | +| `access_url` | [healthcheck.AccessURLReport](#healthcheckaccessurlreport) | false | | | +| `coder_version` | string | false | | The Coder version of the server that the report was generated on. | +| `database` | [healthcheck.DatabaseReport](#healthcheckdatabasereport) | false | | | +| `derp` | [healthcheck.DERPReport](#healthcheckderpreport) | false | | | +| `failing_sections` | array of string | false | | Failing sections is a list of sections that have failed their healthcheck. | +| `healthy` | boolean | false | | Healthy is true if the report returns no errors. | +| `time` | string | false | | Time is the time the report was generated at. | +| `websocket` | [healthcheck.WebsocketReport](#healthcheckwebsocketreport) | false | | | ## healthcheck.WebsocketReport ```json { - "error": null, + "error": "string", "healthy": true, "response": { "body": "string", @@ -6245,7 +6250,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | Name | Type | Required | Restrictions | Description | | ---------- | -------------------------------------------------------------- | -------- | ------------ | ----------- | -| `error` | any | false | | | +| `error` | string | false | | | | `healthy` | boolean | false | | | | `response` | [healthcheck.WebsocketResponse](#healthcheckwebsocketresponse) | false | | |