diff --git a/coderd/debug.go b/coderd/debug.go index eb48fd66b0308..1e50b91ba69d3 100644 --- a/coderd/debug.go +++ b/coderd/debug.go @@ -2,6 +2,7 @@ package coderd import ( "context" + "fmt" "net/http" "time" @@ -37,7 +38,7 @@ func (api *API) debugDeploymentHealth(rw http.ResponseWriter, r *http.Request) { // Get cached report if it exists. if report := api.healthCheckCache.Load(); report != nil { if time.Since(report.Time) < api.HealthcheckRefresh { - httpapi.WriteIndent(ctx, rw, http.StatusOK, report) + formatHealthcheck(ctx, rw, r, report) return } } @@ -59,11 +60,36 @@ func (api *API) debugDeploymentHealth(rw http.ResponseWriter, r *http.Request) { }) return case res := <-resChan: - httpapi.WriteIndent(ctx, rw, http.StatusOK, res.Val) + formatHealthcheck(ctx, rw, r, res.Val) return } } +func formatHealthcheck(ctx context.Context, rw http.ResponseWriter, r *http.Request, hc *healthcheck.Report) { + format := r.URL.Query().Get("format") + switch format { + case "text": + rw.Header().Set("Content-Type", "text/plain; charset=utf-8") + rw.WriteHeader(http.StatusOK) + + _, _ = fmt.Fprintln(rw, "time:", hc.Time.Format(time.RFC3339)) + _, _ = fmt.Fprintln(rw, "healthy:", hc.Healthy) + _, _ = fmt.Fprintln(rw, "derp:", hc.DERP.Healthy) + _, _ = fmt.Fprintln(rw, "access_url:", hc.AccessURL.Healthy) + _, _ = fmt.Fprintln(rw, "websocket:", hc.Websocket.Healthy) + _, _ = fmt.Fprintln(rw, "database:", hc.Database.Healthy) + + case "", "json": + httpapi.WriteIndent(ctx, rw, http.StatusOK, hc) + + default: + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: fmt.Sprintf("Invalid format option %q.", format), + Detail: "Allowed values are: \"json\", \"simple\".", + }) + } +} + // For some reason the swagger docs need to be attached to a function. // // @Summary Debug Info Websocket Test diff --git a/coderd/debug_test.go b/coderd/debug_test.go index 242d271b297b7..f9241a303bcd4 100644 --- a/coderd/debug_test.go +++ b/coderd/debug_test.go @@ -12,6 +12,7 @@ import ( "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/healthcheck" + "github.com/coder/coder/v2/coderd/healthcheck/derphealth" "github.com/coder/coder/v2/testutil" ) @@ -34,7 +35,7 @@ func TestDebugHealth(t *testing.T) { defer cancel() sessionToken = client.SessionToken() - res, err := client.Request(ctx, "GET", "/debug/health", nil) + res, err := client.Request(ctx, "GET", "/api/v2/debug/health", nil) require.NoError(t, err) defer res.Body.Close() _, _ = io.ReadAll(res.Body) @@ -106,6 +107,41 @@ func TestDebugHealth(t *testing.T) { require.Equal(t, http.StatusOK, res.StatusCode) require.Equal(t, 1, calls) }) + + t.Run("Text", func(t *testing.T) { + t.Parallel() + + var ( + ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort) + sessionToken string + client = coderdtest.New(t, &coderdtest.Options{ + HealthcheckFunc: func(_ context.Context, apiKey string) *healthcheck.Report { + assert.Equal(t, sessionToken, apiKey) + return &healthcheck.Report{ + Time: time.Now(), + Healthy: true, + DERP: derphealth.Report{Healthy: true}, + } + }, + }) + _ = coderdtest.CreateFirstUser(t, client) + ) + defer cancel() + + sessionToken = client.SessionToken() + res, err := client.Request(ctx, "GET", "/api/v2/debug/health?format=text", nil) + require.NoError(t, err) + defer res.Body.Close() + resB, _ := io.ReadAll(res.Body) + require.Equal(t, http.StatusOK, res.StatusCode) + + resStr := string(resB) + assert.Contains(t, resStr, "healthy: true") + assert.Contains(t, resStr, "derp: true") + assert.Contains(t, resStr, "access_url: false") + assert.Contains(t, resStr, "websocket: false") + assert.Contains(t, resStr, "database: false") + }) } func TestDebugWebsocket(t *testing.T) {