Skip to content

feat(cli): support bundle: dump healthcheck summary #12963

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 16, 2024
Prev Previous commit
Next Next commit
feat(codersdk/healthsdk): add Summarize() method
  • Loading branch information
johnstcn committed Apr 15, 2024
commit 185a915e6ea35f42a562456c8434f70cb365ce38
43 changes: 43 additions & 0 deletions codersdk/healthsdk/healthsdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"net/http"
"strings"
"time"

"golang.org/x/xerrors"
Expand Down Expand Up @@ -118,6 +119,18 @@ type HealthcheckReport struct {
CoderVersion string `json:"coder_version"`
}

// Summarize returns a summary of all errors and warnings of components of HealthcheckReport.
func (r *HealthcheckReport) Summarize() []string {
var msgs []string
msgs = append(msgs, r.AccessURL.Summarize("Access URL:")...)
msgs = append(msgs, r.Database.Summarize("Database:")...)
msgs = append(msgs, r.DERP.Summarize("DERP:")...)
msgs = append(msgs, r.ProvisionerDaemons.Summarize("Provisioner Daemons:")...)
msgs = append(msgs, r.Websocket.Summarize("Websocket:")...)
msgs = append(msgs, r.WorkspaceProxy.Summarize("Workspace Proxies:")...)
return msgs
}

// BaseReport holds fields common to various health reports.
type BaseReport struct {
// Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.
Expand All @@ -128,6 +141,36 @@ type BaseReport struct {
Dismissed bool `json:"dismissed"`
}

// Summarize returns a list of strings containing the errors and warnings of BaseReport, if present.
// All strings are prefixed with prefix.
func (b *BaseReport) Summarize(prefix string) []string {
if b == nil {
return []string{}
}
var msgs []string
if b.Error != nil {
var sb strings.Builder
if prefix != "" {
_, _ = sb.WriteString(prefix)
_, _ = sb.WriteString(" ")
}
_, _ = sb.WriteString("Error: ")
_, _ = sb.WriteString(*b.Error)
msgs = append(msgs, sb.String())
}
for _, warn := range b.Warnings {
var sb strings.Builder
if prefix != "" {
_, _ = sb.WriteString(prefix)
_, _ = sb.WriteString(" ")
}
_, _ = sb.WriteString("Warn: ")
_, _ = sb.WriteString(warn.String())
msgs = append(msgs, sb.String())
}
return msgs
}

// AccessURLReport shows the results of performing a HTTP_GET to the /healthz endpoint through the configured access URL.
type AccessURLReport struct {
BaseReport
Expand Down
127 changes: 127 additions & 0 deletions codersdk/healthsdk/healthsdk_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package healthsdk_test

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/coder/coder/v2/coderd/healthcheck/health"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk/healthsdk"
)

func TestSummarize(t *testing.T) {
t.Parallel()

t.Run("HealthcheckReport", func(t *testing.T) {
unhealthy := healthsdk.BaseReport{
Error: ptr.Ref("test error"),
Warnings: []health.Message{{Code: "TEST", Message: "testing"}},
}
hr := healthsdk.HealthcheckReport{
AccessURL: healthsdk.AccessURLReport{
BaseReport: unhealthy,
},
Database: healthsdk.DatabaseReport{
BaseReport: unhealthy,
},
DERP: healthsdk.DERPHealthReport{
BaseReport: unhealthy,
},
ProvisionerDaemons: healthsdk.ProvisionerDaemonsReport{
BaseReport: unhealthy,
},
Websocket: healthsdk.WebsocketReport{
BaseReport: unhealthy,
},
WorkspaceProxy: healthsdk.WorkspaceProxyReport{
BaseReport: unhealthy,
},
}
expected := []string{
"Access URL: Error: test error",
"Access URL: Warn: TEST: testing",
"Database: Error: test error",
"Database: Warn: TEST: testing",
"DERP: Error: test error",
"DERP: Warn: TEST: testing",
"Provisioner Daemons: Error: test error",
"Provisioner Daemons: Warn: TEST: testing",
"Websocket: Error: test error",
"Websocket: Warn: TEST: testing",
"Workspace Proxies: Error: test error",
"Workspace Proxies: Warn: TEST: testing",
}
actual := hr.Summarize()
assert.Equal(t, expected, actual)
})

for _, tt := range []struct {
name string
br healthsdk.BaseReport
pfx string
expected []string
}{
{
name: "empty",
br: healthsdk.BaseReport{},
pfx: "",
expected: []string{},
},
{
name: "no prefix",
br: healthsdk.BaseReport{
Error: ptr.Ref("testing"),
Warnings: []health.Message{
{
Code: "TEST01",
Message: "testing one",
},
{
Code: "TEST02",
Message: "testing two",
},
},
},
pfx: "",
expected: []string{
"Error: testing",
"Warn: TEST01: testing one",
"Warn: TEST02: testing two",
},
},
{
name: "prefix",
br: healthsdk.BaseReport{
Error: ptr.Ref("testing"),
Warnings: []health.Message{
{
Code: "TEST01",
Message: "testing one",
},
{
Code: "TEST02",
Message: "testing two",
},
},
},
pfx: "TEST:",
expected: []string{
"TEST: Error: testing",
"TEST: Warn: TEST01: testing one",
"TEST: Warn: TEST02: testing two",
},
},
} {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
actual := tt.br.Summarize(tt.pfx)
if len(tt.expected) == 0 {
assert.Empty(t, actual)
return
}
assert.Equal(t, tt.expected, actual)
})
}
}