Skip to content

Commit 2b574e2

Browse files
authored
feat: add dismissed property to the healthcheck section (coder#10940)
1 parent d374bec commit 2b574e2

19 files changed

+225
-38
lines changed

coderd/apidoc/docs.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/prometheus/client_golang/prometheus"
2626
httpSwagger "github.com/swaggo/http-swagger/v2"
2727
"go.opentelemetry.io/otel/trace"
28+
"golang.org/x/exp/slices"
2829
"golang.org/x/xerrors"
2930
"google.golang.org/api/idtoken"
3031
"storj.io/drpc/drpcmux"
@@ -407,24 +408,30 @@ func New(options *Options) *API {
407408

408409
if options.HealthcheckFunc == nil {
409410
options.HealthcheckFunc = func(ctx context.Context, apiKey string) *healthcheck.Report {
411+
dismissedHealthchecks := loadDismissedHealthchecks(ctx, options.Database, options.Logger)
410412
return healthcheck.Run(ctx, &healthcheck.ReportOptions{
411413
Database: healthcheck.DatabaseReportOptions{
412414
DB: options.Database,
413415
Threshold: options.DeploymentValues.Healthcheck.ThresholdDatabase.Value(),
416+
Dismissed: slices.Contains(dismissedHealthchecks, healthcheck.SectionDatabase),
414417
},
415418
Websocket: healthcheck.WebsocketReportOptions{
416419
AccessURL: options.AccessURL,
417420
APIKey: apiKey,
421+
Dismissed: slices.Contains(dismissedHealthchecks, healthcheck.SectionWebsocket),
418422
},
419423
AccessURL: healthcheck.AccessURLReportOptions{
420424
AccessURL: options.AccessURL,
425+
Dismissed: slices.Contains(dismissedHealthchecks, healthcheck.SectionAccessURL),
421426
},
422427
DerpHealth: derphealth.ReportOptions{
423-
DERPMap: api.DERPMap(),
428+
DERPMap: api.DERPMap(),
429+
Dismissed: slices.Contains(dismissedHealthchecks, healthcheck.SectionDERP),
424430
},
425431
WorkspaceProxy: healthcheck.WorkspaceProxyReportOptions{
426432
CurrentVersion: buildinfo.Version(),
427433
WorkspaceProxiesFetchUpdater: *(options.WorkspaceProxiesFetchUpdater).Load(),
434+
Dismissed: slices.Contains(dismissedHealthchecks, healthcheck.SectionWorkspaceProxy),
428435
},
429436
})
430437
}

coderd/debug.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ package coderd
33
import (
44
"bytes"
55
"context"
6+
"database/sql"
67
"encoding/json"
78
"fmt"
89
"net/http"
910
"time"
1011

12+
"github.com/google/uuid"
1113
"golang.org/x/exp/slices"
1214
"golang.org/x/xerrors"
1315

14-
"github.com/google/uuid"
16+
"cdr.dev/slog"
1517

1618
"github.com/coder/coder/v2/coderd/audit"
1719
"github.com/coder/coder/v2/coderd/database"
@@ -253,3 +255,19 @@ func validateHealthSettings(settings codersdk.HealthSettings) error {
253255
// @Router /debug/ws [get]
254256
// @x-apidocgen {"skip": true}
255257
func _debugws(http.ResponseWriter, *http.Request) {} //nolint:unused
258+
259+
func loadDismissedHealthchecks(ctx context.Context, db database.Store, logger slog.Logger) []string {
260+
dismissedHealthchecks := []string{}
261+
settingsJSON, err := db.GetHealthSettings(ctx)
262+
if err == nil {
263+
var settings codersdk.HealthSettings
264+
err = json.Unmarshal([]byte(settingsJSON), &settings)
265+
if len(settings.DismissedHealthchecks) > 0 {
266+
dismissedHealthchecks = settings.DismissedHealthchecks
267+
}
268+
}
269+
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
270+
logger.Error(ctx, "unable to fetch health settings: %w", err)
271+
}
272+
return dismissedHealthchecks
273+
}

coderd/healthcheck/accessurl.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import (
1616
// @typescript-generate AccessURLReport
1717
type AccessURLReport struct {
1818
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
19-
Healthy bool `json:"healthy"`
20-
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
21-
Warnings []string `json:"warnings"`
19+
Healthy bool `json:"healthy"`
20+
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
21+
Warnings []string `json:"warnings"`
22+
Dismissed bool `json:"dismissed"`
2223

2324
AccessURL string `json:"access_url"`
2425
Reachable bool `json:"reachable"`
@@ -30,6 +31,8 @@ type AccessURLReport struct {
3031
type AccessURLReportOptions struct {
3132
AccessURL *url.URL
3233
Client *http.Client
34+
35+
Dismissed bool
3336
}
3437

3538
func (r *AccessURLReport) Run(ctx context.Context, opts *AccessURLReportOptions) {
@@ -38,6 +41,8 @@ func (r *AccessURLReport) Run(ctx context.Context, opts *AccessURLReportOptions)
3841

3942
r.Severity = health.SeverityOK
4043
r.Warnings = []string{}
44+
r.Dismissed = opts.Dismissed
45+
4146
if opts.AccessURL == nil {
4247
r.Error = ptr.Ref("access URL is nil")
4348
r.Severity = health.SeverityError

coderd/healthcheck/accessurl_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,23 @@ func TestAccessURL(t *testing.T) {
109109
require.NotNil(t, report.Error)
110110
assert.Contains(t, *report.Error, expErr.Error())
111111
})
112+
113+
t.Run("DismissedError", func(t *testing.T) {
114+
t.Parallel()
115+
116+
var (
117+
ctx, cancel = context.WithCancel(context.Background())
118+
report healthcheck.AccessURLReport
119+
)
120+
defer cancel()
121+
122+
report.Run(ctx, &healthcheck.AccessURLReportOptions{
123+
Dismissed: true,
124+
})
125+
126+
assert.True(t, report.Dismissed)
127+
assert.Equal(t, health.SeverityError, report.Severity)
128+
})
112129
}
113130

114131
type roundTripFunc func(r *http.Request) (*http.Response, error)

coderd/healthcheck/database.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ const (
1818
// @typescript-generate DatabaseReport
1919
type DatabaseReport struct {
2020
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
21-
Healthy bool `json:"healthy"`
22-
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
23-
Warnings []string `json:"warnings"`
21+
Healthy bool `json:"healthy"`
22+
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
23+
Warnings []string `json:"warnings"`
24+
Dismissed bool `json:"dismissed"`
2425

2526
Reachable bool `json:"reachable"`
2627
Latency string `json:"latency"`
@@ -32,11 +33,15 @@ type DatabaseReport struct {
3233
type DatabaseReportOptions struct {
3334
DB database.Store
3435
Threshold time.Duration
36+
37+
Dismissed bool
3538
}
3639

3740
func (r *DatabaseReport) Run(ctx context.Context, opts *DatabaseReportOptions) {
3841
r.Warnings = []string{}
3942
r.Severity = health.SeverityOK
43+
r.Dismissed = opts.Dismissed
44+
4045
r.ThresholdMS = opts.Threshold.Milliseconds()
4146
if r.ThresholdMS == 0 {
4247
r.ThresholdMS = DatabaseDefaultThreshold.Milliseconds()

coderd/healthcheck/database_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,26 @@ func TestDatabase(t *testing.T) {
6767
assert.Contains(t, *report.Error, err.Error())
6868
})
6969

70+
t.Run("DismissedError", func(t *testing.T) {
71+
t.Parallel()
72+
73+
var (
74+
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
75+
report = healthcheck.DatabaseReport{}
76+
db = dbmock.NewMockStore(gomock.NewController(t))
77+
err = xerrors.New("ping error")
78+
)
79+
defer cancel()
80+
81+
db.EXPECT().Ping(gomock.Any()).Return(time.Duration(0), err)
82+
83+
report.Run(ctx, &healthcheck.DatabaseReportOptions{DB: db, Dismissed: true})
84+
85+
assert.Equal(t, health.SeverityError, report.Severity)
86+
assert.True(t, report.Dismissed)
87+
require.NotNil(t, report.Error)
88+
})
89+
7090
t.Run("Median", func(t *testing.T) {
7191
t.Parallel()
7292

coderd/healthcheck/derphealth/derp.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ const (
3636
// @typescript-generate Report
3737
type Report struct {
3838
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
39-
Healthy bool `json:"healthy"`
40-
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
41-
Warnings []string `json:"warnings"`
39+
Healthy bool `json:"healthy"`
40+
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
41+
Warnings []string `json:"warnings"`
42+
Dismissed bool `json:"dismissed"`
4243

4344
Regions map[int]*RegionReport `json:"regions"`
4445

@@ -95,15 +96,18 @@ type StunReport struct {
9596
}
9697

9798
type ReportOptions struct {
99+
Dismissed bool
100+
98101
DERPMap *tailcfg.DERPMap
99102
}
100103

101104
func (r *Report) Run(ctx context.Context, opts *ReportOptions) {
102105
r.Healthy = true
103106
r.Severity = health.SeverityOK
107+
r.Warnings = []string{}
108+
r.Dismissed = opts.Dismissed
104109

105110
r.Regions = map[int]*RegionReport{}
106-
r.Warnings = []string{}
107111

108112
wg := &sync.WaitGroup{}
109113
mu := sync.Mutex{}

coderd/healthcheck/derphealth/derp_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,15 @@ func TestDERP(t *testing.T) {
120120
}},
121121
},
122122
}},
123+
Dismissed: true, // Let's sneak an extra unit test
123124
}
124125
)
125126

126127
report.Run(ctx, opts)
127128

128129
assert.True(t, report.Healthy)
129130
assert.Equal(t, health.SeverityWarning, report.Severity)
131+
assert.True(t, report.Dismissed)
130132
for _, region := range report.Regions {
131133
assert.True(t, region.Healthy)
132134
assert.True(t, region.NodeReports[0].Healthy)

coderd/healthcheck/websocket.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,35 @@ import (
1515
"github.com/coder/coder/v2/coderd/healthcheck/health"
1616
)
1717

18-
type WebsocketReportOptions struct {
19-
APIKey string
20-
AccessURL *url.URL
21-
HTTPClient *http.Client
22-
}
23-
2418
// @typescript-generate WebsocketReport
2519
type WebsocketReport struct {
2620
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
27-
Healthy bool `json:"healthy"`
28-
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
29-
Warnings []string `json:"warnings"`
21+
Healthy bool `json:"healthy"`
22+
Severity health.Severity `json:"severity" enums:"ok,warning,error"`
23+
Warnings []string `json:"warnings"`
24+
Dismissed bool `json:"dismissed"`
3025

3126
Body string `json:"body"`
3227
Code int `json:"code"`
3328
Error *string `json:"error"`
3429
}
3530

31+
type WebsocketReportOptions struct {
32+
APIKey string
33+
AccessURL *url.URL
34+
HTTPClient *http.Client
35+
36+
Dismissed bool
37+
}
38+
3639
func (r *WebsocketReport) Run(ctx context.Context, opts *WebsocketReportOptions) {
3740
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
3841
defer cancel()
3942

4043
r.Severity = health.SeverityOK
4144
r.Warnings = []string{}
45+
r.Dismissed = opts.Dismissed
46+
4247
u, err := opts.AccessURL.Parse("/api/v2/debug/ws")
4348
if err != nil {
4449
r.Error = convertError(xerrors.Errorf("parse access url: %w", err))

0 commit comments

Comments
 (0)