Skip to content
This repository was archived by the owner on Nov 14, 2024. It is now read-only.

feat: initial implementation of coder-doctor #3

Merged
merged 11 commits into from
Aug 16, 2021
Merged
Prev Previous commit
Next Next commit
wip
  • Loading branch information
jawnsy committed Aug 13, 2021
commit 84882af66d24f0caf2477fb9d85af0eefa33b78d
13 changes: 13 additions & 0 deletions internal/api/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package api

// ErrorResult returns a CheckResult when an error occurs.
func ErrorResult(name string, summary string, err error) *CheckResult {
return &CheckResult{
Name: name,
State: StateFailed,
Summary: summary,
Details: map[string]interface{}{
"error": err,
},
}
}
21 changes: 21 additions & 0 deletions internal/api/error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package api_test

import (
"errors"
"testing"

"cdr.dev/slog/sloggers/slogtest/assert"
"github.com/cdr/coder-doctor/internal/api"
)

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

err := errors.New("failed to connect to database")
res := api.ErrorResult("check-name", "check failed", err)

assert.Equal(t, "name matches", "check-name", res.Name)
assert.Equal(t, "state matches", api.StateFailed, res.State)
assert.Equal(t, "summary matches", "check failed", res.Summary)
assert.Equal(t, "error matches", err, res.Details["error"])
}
72 changes: 72 additions & 0 deletions internal/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package api

import (
"context"
"fmt"

"golang.org/x/xerrors"
)

type Checker interface {
Expand All @@ -19,6 +22,8 @@ type Checker interface {
Run(context.Context) CheckResults
}

var _ = fmt.Stringer(StatePassed)

type CheckState int
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we make these integers rather than strings?

That way we could eliminate the string function, and set the values to the const names.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There wasn't a particular reason, no! I thought that integers were just the common way to do enums like this in Go.

A nice benefit of this is that the filtering can be done with bitwise operations (to determine whether to log each level), but of course we could do that with a set of bools or something too: https://github.com/cdr/coder-doctor/blob/31cb1c565fc42be9b40d9c68e634625287086b0b/internal/filterwriter/filter.go#L42-L60

I suppose another benefit is that comparisons should be faster with integers, because then it's just a numeric equality instead of string comparison

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neato. Appreciate the context, and I'd agree with you here!


const (
Expand All @@ -41,6 +46,73 @@ const (
StateSkipped
)

func (s CheckState) MustEmoji() string {
emoji, err := s.Emoji()
if err != nil {
panic(err.Error())
}
return emoji
}

func (s CheckState) Emoji() (string, error) {
switch s {
case StatePassed:
return "✅", nil
case StateWarning:
return "⚠️", nil
case StateFailed:
return "❌", nil
case StateInfo:
return "ℹ️", nil
case StateSkipped:
return "🤔", nil
}

return "", xerrors.Errorf("unknown state: %d", s)
}

func (s CheckState) MustText() string {
text, err := s.Text()
if err != nil {
panic(err.Error())
}
return text
}

func (s CheckState) Text() (string, error) {
switch s {
case StatePassed:
return "PASS", nil
case StateWarning:
return "WARN", nil
case StateFailed:
return "FAIL", nil
case StateInfo:
return "INFO", nil
case StateSkipped:
return "SKIP", nil
}

return "", xerrors.Errorf("unknown state: %d", s)
}

func (s CheckState) String() string {
switch s {
case StatePassed:
return "StatePassed"
case StateWarning:
return "StateWarning"
case StateFailed:
return "StateFailed"
case StateInfo:
return "StateInfo"
case StateSkipped:
return "StateSkipped"
}

panic(fmt.Sprintf("unknown state: %d", s))
}

type CheckResult struct {
Name string
State CheckState
Expand Down
35 changes: 35 additions & 0 deletions internal/api/types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package api_test

import (
"testing"

"cdr.dev/slog/sloggers/slogtest/assert"
"github.com/cdr/coder-doctor/internal/api"
)

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

states := []api.CheckState{
api.StatePassed,
api.StateWarning,
api.StateFailed,
api.StateInfo,
api.StateSkipped,
}

for _, state := range states {
t.Run(state.String(), func(t *testing.T) {
emoji, err := state.Emoji()
assert.Success(t, "state.Emoji() error non-nil", err)
assert.True(t, "state.Emoji() is non-empty", len(emoji) > 0)

text, err := state.Text()
assert.Success(t, "state.Text() error non-nil", err)
assert.True(t, "state.Text() is non-empty", len(text) > 0)

str := state.String()
assert.True(t, "state.String() is non-empty", len(str) > 0)
})
}
}
46 changes: 0 additions & 46 deletions internal/api/util.go

This file was deleted.

14 changes: 14 additions & 0 deletions internal/api/writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package api

// ResultWriter writes the given result to a configured output.
type ResultWriter interface {
WriteResult(result *CheckResult) error
}

// DiscardWriter is a writer that discards all results.
type DiscardWriter struct {
}

func (*DiscardWriter) WriteResult(result *CheckResult) error {
return nil
}
26 changes: 26 additions & 0 deletions internal/api/writer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package api_test

import (
"testing"

"cdr.dev/slog/sloggers/slogtest/assert"
"github.com/cdr/coder-doctor/internal/api"
)

var _ = api.ResultWriter(&api.DiscardWriter{})

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

w := &api.DiscardWriter{}
err := w.WriteResult(nil)

assert.Success(t, "discard with nil result", err)

err = w.WriteResult(&api.CheckResult{
Name: "test-check",
State: api.StatePassed,
Summary: "check successful",
})
assert.Success(t, "discard with success result", err)
}
66 changes: 0 additions & 66 deletions internal/cmd/check/kube/kubernetes.go

This file was deleted.

19 changes: 13 additions & 6 deletions internal/cmd/check/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"

kclient "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes"
// Kubernetes authentication plugins
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
_ "k8s.io/client-go/plugin/pkg/client/auth/exec"
Expand All @@ -17,8 +17,8 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
"k8s.io/client-go/tools/clientcmd"

"github.com/cdr/coder-doctor/internal/api"
"github.com/cdr/coder-doctor/internal/checks/kube"
"github.com/cdr/coder-doctor/internal/humanwriter"
)

func NewCommand() *cobra.Command {
Expand Down Expand Up @@ -78,7 +78,7 @@ func run(cmd *cobra.Command, _ []string) error {
return err
}

clientset, err := kclient.NewForConfig(config)
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return err
}
Expand All @@ -99,6 +99,8 @@ func run(cmd *cobra.Command, _ []string) error {
return err
}

// TODO: this is pretty arbitrary, use a defined verbosity similar to
// kubectl
if verbosity > 5 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, I usually set --verbosity=99 or something if I see something like that and want to be extra-verbose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kubectl has a really nicely-defined list, with criteria for what goes where -- I think we should adopt similar definitions, because otherwise (as a developer), it's difficult to know what level to log things, and it makes the level kinda useless for users: https://kubernetes.io/docs/reference/kubectl/_print/#kubectl-output-verbosity-and-debugging

log = log.Leveled(slog.LevelDebug)
}
Expand All @@ -109,10 +111,15 @@ func run(cmd *cobra.Command, _ []string) error {
kube.WithLogger(log),
)

writer := humanwriter.New()

results := checker.Run(cmd.Context())
err = api.WriteResults(cmd.OutOrStdout(), results)
if err != nil {
return xerrors.Errorf("failed to write results to stdout: %w", err)
for _, result := range results {
err = writer.WriteResult(result)
if err != nil {
return xerrors.Errorf("failed to write results to stdout: %w", err)
}

}

return nil
Expand Down
Loading