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

Conversation

jawnsy
Copy link
Contributor

@jawnsy jawnsy commented Aug 7, 2021

No description provided.

@shortcut-integration
Copy link

This pull request has been linked to Clubhouse Story #15961: Initial implementation of coder-doctor framework.

@jawnsy jawnsy self-assigned this Aug 7, 2021
@kylecarbs
Copy link
Member

Such a massive fan of this! We can use this package to validate clusters are properly configured when being added too. 🥳🥳🥳

},
}

for _, test := range tests {
Copy link
Member

Choose a reason for hiding this comment

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

you should autogenerate some tests (with dummy data for version fields we don't care about) for all of our supported versions

@jawnsy jawnsy force-pushed the jawnsy/ch15961/initial-implementation branch from 5fb3b2b to f2f9988 Compare August 14, 2021 02:17

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!

@jawnsy jawnsy requested a review from johnstcn August 16, 2021 15:09
@jawnsy jawnsy marked this pull request as ready for review August 16, 2021 15:12
@jawnsy
Copy link
Contributor Author

jawnsy commented Aug 16, 2021

@johnstcn I wanted to get this initial version merged, can you take another look? I think you'd be the perfect reviewer for this since you've been working with the CLI more lately.

It's not complete and just checks the version for now, but it's going to be harder to iterate on this with the existing PR due to the size. If you want to test locally, you should be able to clone the branch and run:

$ go run . check kubernetes --verbosity=10
2021-08-16 15:10:20.462 [DEBUG] <version.go:67> selected coder version  {"requested": "1.21.0", "selected": "1.21.0"}
PASS Coder 1.21.0 supports Kubernetes 1.19.0 to 1.22.0 (server version 1.20.8-gke.900)

This will use your current kubectl context (or you can also specify flags like --context)

emoji, err := state.Emoji()
assert.Success(t, "state.Emoji() error non-nil", err)
assert.True(t, "state.Emoji() is non-empty", len(emoji) > 0)
_ = state.MustEmoji()
Copy link
Member

Choose a reason for hiding this comment

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

❤️ this


type CoderVersionRequirement struct {
CoderVersion *semver.Version
KubernetesVersionMin *semver.Version
Copy link
Member

Choose a reason for hiding this comment

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

nit: we can probably use a semver.Constraint instead of specifying new versions, but this works for now.
https://pkg.go.dev/github.com/Masterminds/semver#hdr-Checking_Version_Constraints

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can, but that API is a little more annoying to use because there's no Must variant. I suppose we could specify all of these as strings and parse at runtime?

Copy link
Member

Choose a reason for hiding this comment

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

We're probably better off doing as much as possible at compile-time!


// 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

Copy link
Member

@johnstcn johnstcn left a comment

Choose a reason for hiding this comment

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

Nice one!

./main check  kubernetes --verbosity 9999
2021-08-16 15:23:50.681 [DEBUG] <version.go:67> selected coder version  {"requested": "1.21.0", "selected": "1.21.0"}
PASS Coder 1.21.0 supports Kubernetes 1.19.0 to 1.22.0 (server version 1.20.8-gke.900)

func (k *KubernetesChecker) Run(ctx context.Context) error {
err := k.writer.WriteResult(k.CheckVersion(ctx))
if err != nil {
return err
Copy link
Member

Choose a reason for hiding this comment

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

Can we add more xerrors.Errorf(... about the place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

excellent point, done!


kubernetesVersion, err := semver.NewVersion(versionInfo.GitVersion)
if err != nil {
return api.ErrorResult(checkName, "failed to parse server version", err)
Copy link
Member

Choose a reason for hiding this comment

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

Can we return a different message here than line 63 e.g. failed to parse git version info from server? Exact wording chef's choice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

excellent catch! changed the error on line 63 to read: failed to unmarshal version info instead, to clarify what it's actually doing


overrides.CurrentContext, err = cmd.Flags().GetString(clientcmd.FlagContext)
if err != nil {
return nil, err
Copy link
Member

Choose a reason for hiding this comment

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

xerrors.Errorf(... and below

var expected string
switch mode {
case humanwriter.OutputModeEmoji:
expected = "👍 human writer check test\n" +
Copy link
Member

Choose a reason for hiding this comment

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

nit: the pass and fail emoji are easy to confuse at a glance, suggest ✅ and ❌ for maximum distinguishability.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm, I don't feel strongly about it and suggest we try it as it is for a bit to see what we think -- we can always change it later


// This uses the RESTClient rather than Discovery().ServerVersion()
// because the latter does not accept a context.
body, err := k.client.Discovery().RESTClient().Get().AbsPath("/version").Do(ctx).Raw()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

on second thought, I think splitting this into a KubernetesVersionChecker struct might be useful, it could use a DiscoveryClient interface or RESTClient as its arg instead of a kubernetes.Interface -- that would make it easier to unit test https://pkg.go.dev/k8s.io/client-go@v0.22.0/kubernetes/typed/discovery/v1#DiscoveryV1Interface

but I think that's something for later

@jawnsy jawnsy merged commit 4a854f0 into main Aug 16, 2021
@jawnsy jawnsy deleted the jawnsy/ch15961/initial-implementation branch August 16, 2021 16:40
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants