Skip to content

chore: handle errors in wsproxy server for cli using buildinfo #11584

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
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add tracing as well for better error debugging
  • Loading branch information
Emyrk committed Jan 11, 2024
commit 96506382ef73290f42db14c37eaba049fd4b74b1
4 changes: 2 additions & 2 deletions cli/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ func (RootCmd) errorExample() *clibase.Cmd {
{
Use: "multi-error",
Handler: func(inv *clibase.Invocation) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

should we hide this command? There's no reason a user would ever need to use this.

Copy link
Member Author

Choose a reason for hiding this comment

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

It is hidden up the stack:

Hidden: true,

return xerrors.Errorf("wrapped %w", errors.Join(
return xerrors.Errorf("wrapped: %w", errors.Join(
xerrors.Errorf("first error: %w", errorWithStackTrace()),
xerrors.Errorf("second error: %w", errorWithStackTrace()),
apiErrorNoHelper,
xerrors.Errorf("wrapped api error: %w", apiErrorNoHelper),
))
},
},
Expand Down
49 changes: 34 additions & 15 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -1016,7 +1016,7 @@ type prettyErrorFormatter struct {
// format formats the error to the console. This error should be human
// readable.
func (p *prettyErrorFormatter) format(err error) {
output, _ := cliHumanFormatError(err, &formatOpts{
output, _ := cliHumanFormatError("", err, &formatOpts{
Verbose: p.verbose,
})
// always trail with a newline
Expand All @@ -1041,7 +1041,7 @@ const indent = " "
// go run main.go exp example-error cmd
// go run main.go exp example-error multi-error
// go run main.go exp example-error validation
func cliHumanFormatError(err error, opts *formatOpts) (string, bool) {
func cliHumanFormatError(from string, err error, opts *formatOpts) (string, bool) {
if opts == nil {
opts = &formatOpts{}
}
Expand All @@ -1053,28 +1053,26 @@ func cliHumanFormatError(err error, opts *formatOpts) (string, bool) {
multiErrors := multi.Unwrap()
if len(multiErrors) == 1 {
// Format as a single error
return cliHumanFormatError(multiErrors[0], opts)
return cliHumanFormatError(from, multiErrors[0], opts)
}
return formatMultiError(multiErrors, opts), true
return formatMultiError(from, multiErrors, opts), true
}

// First check for sentinel errors that we want to handle specially.
// Order does matter! We want to check for the most specific errors first.
//var sdkError *codersdk.Error
//if errors.As(err, &sdkError) {
if sdkError, ok := err.(*codersdk.Error); ok {
return formatCoderSDKError(sdkError, opts), true
return formatCoderSDKError(from, sdkError, opts), true
}

//var cmdErr *clibase.RunCommandError
//if errors.As(err, &cmdErr) {
if cmdErr, ok := err.(*clibase.RunCommandError); ok {
// no need to pass the "from" context to this since it is always
// top level. We care about what is below this.
return formatRunCommandError(cmdErr, opts), true
}

uw, ok := err.(interface{ Unwrap() error })
if ok {
msg, special := cliHumanFormatError(uw.Unwrap(), opts)
msg, special := cliHumanFormatError(from+traceError(err), uw.Unwrap(), opts)
if special {
return msg, special
}
Expand All @@ -1100,16 +1098,20 @@ func cliHumanFormatError(err error, opts *formatOpts) (string, bool) {
// <verbose error message>
// 2. <heading error message>
// <verbose error message>
func formatMultiError(multi []error, opts *formatOpts) string {
func formatMultiError(from string, multi []error, opts *formatOpts) string {
var errorStrings []string
for _, err := range multi {
msg, _ := cliHumanFormatError(err, opts)
msg, _ := cliHumanFormatError("", err, opts)
errorStrings = append(errorStrings, msg)
}

// Write errors out
var str strings.Builder
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("%d errors encountered:", len(multi))))
var traceMsg string
if opts.Verbose {
traceMsg = fmt.Sprintf("Trace=[%s])", from)
}
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("%d errors encountered: %s", len(multi), traceMsg)))
for i, errStr := range errorStrings {
// Indent each error
errStr = strings.ReplaceAll(errStr, "\n", "\n"+indent)
Expand Down Expand Up @@ -1138,7 +1140,7 @@ func formatRunCommandError(err *clibase.RunCommandError, opts *formatOpts) strin
var str strings.Builder
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("Encountered an error running %q", err.Cmd.FullName())))

msgString, special := cliHumanFormatError(err.Err, opts)
msgString, special := cliHumanFormatError("", err.Err, opts)
_, _ = str.WriteString("\n")
if special {
_, _ = str.WriteString(msgString)
Expand All @@ -1151,11 +1153,15 @@ func formatRunCommandError(err *clibase.RunCommandError, opts *formatOpts) strin

// formatCoderSDKError come from API requests. In verbose mode, add the
// request debug information.
func formatCoderSDKError(err *codersdk.Error, opts *formatOpts) string {
func formatCoderSDKError(from string, err *codersdk.Error, opts *formatOpts) string {
var str strings.Builder
if opts.Verbose {
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("API request error to \"%s:%s\". Status code %d", err.Method(), err.URL(), err.StatusCode())))
_, _ = str.WriteString("\n")
if from != "" {
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("Trace=[%s]", from)))
_, _ = str.WriteString("\n")
}
}

_, _ = str.WriteString(pretty.Sprint(headLineStyle(), err.Message))
Expand All @@ -1171,6 +1177,19 @@ func formatCoderSDKError(err *codersdk.Error, opts *formatOpts) string {
return str.String()
}

// traceError is a helper function that aides developers debugging failed cli
// commands. When we pretty print errors, we lose the context in which they came.
// This function adds the context back. Unfortunately there is no easy way to get
// the prefix to: "error string: %w", so we do a bit of string manipulation.
func traceError(err error) string {
if uw, ok := err.(interface{ Unwrap() error }); ok {
a, b := err.Error(), uw.Unwrap().Error()
c := strings.TrimSuffix(a, b)
return c
}
return err.Error()
}

// These styles are arbitrary.
func headLineStyle() pretty.Style {
return cliui.DefaultStyles.Error
Expand Down
4 changes: 2 additions & 2 deletions enterprise/wsproxy/wsproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,10 @@ func New(ctx context.Context, opts *Options) (*Server, error) {
// TODO: Probably do some version checking here
info, err := client.SDKClient.BuildInfo(ctx)
if err != nil {
return nil, errors.Join(
return nil, fmt.Errorf("buildinfo: %w", errors.Join(
fmt.Errorf("unable to fetch build info from primary coderd. Are you sure %q is a coderd instance?", opts.DashboardURL),
err,
)
))
}
if info.WorkspaceProxy {
return nil, xerrors.Errorf("%q is a workspace proxy, not a primary coderd instance", opts.DashboardURL)
Expand Down