diff --git a/cli/root.go b/cli/root.go index 862f290d66..b38b7c7244 100644 --- a/cli/root.go +++ b/cli/root.go @@ -455,6 +455,11 @@ func checkVersions(cmd *cobra.Command, client *codersdk.Client) error { clientVersion := buildinfo.Version() info, err := client.BuildInfo(cmd.Context()) + // Avoid printing errors that are connection-related. + if codersdk.IsConnectionErr(err) { + return nil + } + if err != nil { return xerrors.Errorf("build info: %w", err) } diff --git a/codersdk/error.go b/codersdk/error.go index 1dd16f98cb..316d15d888 100644 --- a/codersdk/error.go +++ b/codersdk/error.go @@ -1,5 +1,11 @@ package codersdk +import ( + "net" + + "golang.org/x/xerrors" +) + // Response represents a generic HTTP response. type Response struct { // Message is an actionable message that depicts actions the request took. @@ -25,3 +31,16 @@ type ValidationError struct { Field string `json:"field" validate:"required"` Detail string `json:"detail" validate:"required"` } + +// IsConnectionErr is a convenience function for checking if the source of an +// error is due to a 'connection refused', 'no such host', etc. +func IsConnectionErr(err error) bool { + var ( + // E.g. no such host + dnsErr *net.DNSError + // Eg. connection refused + opErr *net.OpError + ) + + return xerrors.As(err, &dnsErr) || xerrors.As(err, &opErr) +} diff --git a/codersdk/error_test.go b/codersdk/error_test.go new file mode 100644 index 0000000000..d03024cbf1 --- /dev/null +++ b/codersdk/error_test.go @@ -0,0 +1,65 @@ +package codersdk_test + +import ( + "net" + "os" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" + + "github.com/coder/coder/codersdk" +) + +func TestIsConnectionErr(t *testing.T) { + t.Parallel() + + type tc = struct { + name string + err error + expectedResult bool + } + + cases := []tc{ + { + // E.g. "no such host" + name: "DNSError", + err: &net.DNSError{ + Err: "no such host", + Name: "foofoo", + Server: "1.1.1.1:53", + IsTimeout: false, + IsTemporary: false, + IsNotFound: true, + }, + expectedResult: true, + }, + { + // E.g. "connection refused" + name: "OpErr", + err: &net.OpError{ + Op: "dial", + Net: "tcp", + Source: nil, + Addr: nil, + Err: &os.SyscallError{}, + }, + expectedResult: true, + }, + { + name: "OpaqueError", + err: xerrors.Errorf("I'm opaque!"), + expectedResult: false, + }, + } + + for _, c := range cases { + c := c + + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + require.Equal(t, c.expectedResult, codersdk.IsConnectionErr(c.err)) + }) + } +} diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 7714ad1965..e80e333b0d 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -250,7 +250,7 @@ export interface PutExtendWorkspaceRequest { readonly deadline: string } -// From codersdk/error.go:4:6 +// From codersdk/error.go:10:6 export interface Response { readonly message: string readonly detail?: string @@ -386,7 +386,7 @@ export interface UsersRequest extends Pagination { readonly q?: string } -// From codersdk/error.go:24:6 +// From codersdk/error.go:30:6 export interface ValidationError { readonly field: string readonly detail: string