Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitf5a9f5c

Browse files
authored
chore: handle errors in wsproxy server for cli using buildinfo (#11584)
Cli errors are pretty formatted. This handles nested pretty types. Before it found the first error it could understand and return that. Now it will print the full error stack with more information.To prevent information loss, a "[Trace=...]" was added to capture some extra error context for debugging.
1 parentaecdafd commitf5a9f5c

File tree

5 files changed

+114
-31
lines changed

5 files changed

+114
-31
lines changed

‎cli/errors.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"errors"
45
"fmt"
56
"net/http"
67
"net/http/httptest"
@@ -43,6 +44,10 @@ func (RootCmd) errorExample() *clibase.Cmd {
4344
//nolint:errorlint,forcetypeassert
4445
apiError.(*codersdk.Error).Helper="Have you tried turning it off and on again?"
4546

47+
//nolint:errorlint,forcetypeassert
48+
apiErrorNoHelper:=apiError.(*codersdk.Error)
49+
apiErrorNoHelper.Helper=""
50+
4651
// Some flags
4752
varmagicWord clibase.String
4853

@@ -65,14 +70,28 @@ func (RootCmd) errorExample() *clibase.Cmd {
6570
// A multi-error
6671
{
6772
Use:"multi-error",
73+
Handler:func(inv*clibase.Invocation)error {
74+
returnxerrors.Errorf("wrapped: %w",errors.Join(
75+
xerrors.Errorf("first error: %w",errorWithStackTrace()),
76+
xerrors.Errorf("second error: %w",errorWithStackTrace()),
77+
xerrors.Errorf("wrapped api error: %w",apiErrorNoHelper),
78+
))
79+
},
80+
},
81+
{
82+
Use:"multi-multi-error",
83+
Short:"This is a multi error inside a multi error",
6884
Handler:func(inv*clibase.Invocation)error {
6985
// Closing the stdin file descriptor will cause the next close
7086
// to fail. This is joined to the returned Command error.
7187
iff,ok:=inv.Stdin.(*os.File);ok {
7288
_=f.Close()
7389
}
7490

75-
returnxerrors.Errorf("some error: %w",errorWithStackTrace())
91+
returnerrors.Join(
92+
xerrors.Errorf("first error: %w",errorWithStackTrace()),
93+
xerrors.Errorf("second error: %w",errorWithStackTrace()),
94+
)
7695
},
7796
},
7897

‎cli/root.go

Lines changed: 75 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,7 +1016,7 @@ type prettyErrorFormatter struct {
10161016
// format formats the error to the console. This error should be human
10171017
// readable.
10181018
func (p*prettyErrorFormatter)format(errerror) {
1019-
output:=cliHumanFormatError(err,&formatOpts{
1019+
output,_:=cliHumanFormatError("",err,&formatOpts{
10201020
Verbose:p.verbose,
10211021
})
10221022
// always trail with a newline
@@ -1030,41 +1030,66 @@ type formatOpts struct {
10301030
constindent=" "
10311031

10321032
// cliHumanFormatError formats an error for the CLI. Newlines and styling are
1033-
// included.
1034-
funccliHumanFormatError(errerror,opts*formatOpts)string {
1033+
// included. The second return value is true if the error is special and the error
1034+
// chain has custom formatting applied.
1035+
//
1036+
// If you change this code, you can use the cli "example-errors" tool to
1037+
// verify all errors still look ok.
1038+
//
1039+
//go run main.go exp example-error <type>
1040+
// go run main.go exp example-error api
1041+
// go run main.go exp example-error cmd
1042+
// go run main.go exp example-error multi-error
1043+
// go run main.go exp example-error validation
1044+
//
1045+
//nolint:errorlint
1046+
funccliHumanFormatError(fromstring,errerror,opts*formatOpts) (string,bool) {
10351047
ifopts==nil {
10361048
opts=&formatOpts{}
10371049
}
1050+
iferr==nil {
1051+
return"<nil>",true
1052+
}
10381053

1039-
//nolint:errorlint
10401054
ifmulti,ok:=err.(interface{Unwrap() []error });ok {
10411055
multiErrors:=multi.Unwrap()
10421056
iflen(multiErrors)==1 {
10431057
// Format as a single error
1044-
returncliHumanFormatError(multiErrors[0],opts)
1058+
returncliHumanFormatError(from,multiErrors[0],opts)
10451059
}
1046-
returnformatMultiError(multiErrors,opts)
1060+
returnformatMultiError(from,multiErrors,opts),true
10471061
}
10481062

10491063
// First check for sentinel errors that we want to handle specially.
10501064
// Order does matter! We want to check for the most specific errors first.
1051-
varsdkError*codersdk.Error
1052-
iferrors.As(err,&sdkError) {
1053-
returnformatCoderSDKError(sdkError,opts)
1065+
ifsdkError,ok:=err.(*codersdk.Error);ok {
1066+
returnformatCoderSDKError(from,sdkError,opts),true
10541067
}
10551068

1056-
varcmdErr*clibase.RunCommandError
1057-
iferrors.As(err,&cmdErr) {
1058-
returnformatRunCommandError(cmdErr,opts)
1069+
ifcmdErr,ok:=err.(*clibase.RunCommandError);ok {
1070+
// no need to pass the "from" context to this since it is always
1071+
// top level. We care about what is below this.
1072+
returnformatRunCommandError(cmdErr,opts),true
10591073
}
10601074

1075+
uw,ok:=err.(interface{Unwrap()error })
1076+
ifok {
1077+
msg,special:=cliHumanFormatError(from+traceError(err),uw.Unwrap(),opts)
1078+
ifspecial {
1079+
returnmsg,special
1080+
}
1081+
}
1082+
// If we got here, that means that the wrapped error chain does not have
1083+
// any special formatting below it. So we want to return the topmost non-special
1084+
// error (which is 'err')
1085+
10611086
// Default just printing the error. Use +v for verbose to handle stack
10621087
// traces of xerrors.
10631088
ifopts.Verbose {
1064-
returnpretty.Sprint(headLineStyle(),fmt.Sprintf("%+v",err))
1089+
returnpretty.Sprint(headLineStyle(),fmt.Sprintf("%+v",err)),false
10651090
}
10661091

1067-
returnpretty.Sprint(headLineStyle(),fmt.Sprintf("%v",err))
1092+
returnpretty.Sprint(headLineStyle(),fmt.Sprintf("%v",err)),false
10681093
}
10691094

10701095
// formatMultiError formats a multi-error. It formats it as a list of errors.
@@ -1075,15 +1100,20 @@ func cliHumanFormatError(err error, opts *formatOpts) string {
10751100
// <verbose error message>
10761101
//2. <heading error message>
10771102
// <verbose error message>
1078-
funcformatMultiError(multi []error,opts*formatOpts)string {
1103+
funcformatMultiError(fromstring,multi []error,opts*formatOpts)string {
10791104
varerrorStrings []string
10801105
for_,err:=rangemulti {
1081-
errorStrings=append(errorStrings,cliHumanFormatError(err,opts))
1106+
msg,_:=cliHumanFormatError("",err,opts)
1107+
errorStrings=append(errorStrings,msg)
10821108
}
10831109

10841110
// Write errors out
10851111
varstr strings.Builder
1086-
_,_=str.WriteString(pretty.Sprint(headLineStyle(),fmt.Sprintf("%d errors encountered:",len(multi))))
1112+
vartraceMsgstring
1113+
iffrom!="" {
1114+
traceMsg=fmt.Sprintf("Trace=[%s])",from)
1115+
}
1116+
_,_=str.WriteString(pretty.Sprint(headLineStyle(),fmt.Sprintf("%d errors encountered: %s",len(multi),traceMsg)))
10871117
fori,errStr:=rangeerrorStrings {
10881118
// Indent each error
10891119
errStr=strings.ReplaceAll(errStr,"\n","\n"+indent)
@@ -1112,24 +1142,30 @@ func formatRunCommandError(err *clibase.RunCommandError, opts *formatOpts) strin
11121142
varstr strings.Builder
11131143
_,_=str.WriteString(pretty.Sprint(headLineStyle(),fmt.Sprintf("Encountered an error running %q",err.Cmd.FullName())))
11141144

1115-
msgString:=fmt.Sprintf("%v",err.Err)
1116-
ifopts.Verbose {
1117-
// '%+v' includes stack traces
1118-
msgString=fmt.Sprintf("%+v",err.Err)
1119-
}
1145+
msgString,special:=cliHumanFormatError("",err.Err,opts)
11201146
_,_=str.WriteString("\n")
1121-
_,_=str.WriteString(pretty.Sprint(tailLineStyle(),msgString))
1147+
ifspecial {
1148+
_,_=str.WriteString(msgString)
1149+
}else {
1150+
_,_=str.WriteString(pretty.Sprint(tailLineStyle(),msgString))
1151+
}
1152+
11221153
returnstr.String()
11231154
}
11241155

11251156
// formatCoderSDKError come from API requests. In verbose mode, add the
11261157
// request debug information.
1127-
funcformatCoderSDKError(err*codersdk.Error,opts*formatOpts)string {
1158+
funcformatCoderSDKError(fromstring,err*codersdk.Error,opts*formatOpts)string {
11281159
varstr strings.Builder
11291160
ifopts.Verbose {
11301161
_,_=str.WriteString(pretty.Sprint(headLineStyle(),fmt.Sprintf("API request error to\"%s:%s\". Status code %d",err.Method(),err.URL(),err.StatusCode())))
11311162
_,_=str.WriteString("\n")
11321163
}
1164+
// Always include this trace. Users can ignore this.
1165+
iffrom!="" {
1166+
_,_=str.WriteString(pretty.Sprint(headLineStyle(),fmt.Sprintf("Trace=[%s]",from)))
1167+
_,_=str.WriteString("\n")
1168+
}
11331169

11341170
_,_=str.WriteString(pretty.Sprint(headLineStyle(),err.Message))
11351171
iferr.Helper!="" {
@@ -1144,6 +1180,21 @@ func formatCoderSDKError(err *codersdk.Error, opts *formatOpts) string {
11441180
returnstr.String()
11451181
}
11461182

1183+
// traceError is a helper function that aides developers debugging failed cli
1184+
// commands. When we pretty print errors, we lose the context in which they came.
1185+
// This function adds the context back. Unfortunately there is no easy way to get
1186+
// the prefix to: "error string: %w", so we do a bit of string manipulation.
1187+
//
1188+
//nolint:errorlint
1189+
functraceError(errerror)string {
1190+
ifuw,ok:=err.(interface{Unwrap()error });ok {
1191+
a,b:=err.Error(),uw.Unwrap().Error()
1192+
c:=strings.TrimSuffix(a,b)
1193+
returnc
1194+
}
1195+
returnerr.Error()
1196+
}
1197+
11471198
// These styles are arbitrary.
11481199
funcheadLineStyle() pretty.Style {
11491200
returncliui.DefaultStyles.Error

‎codersdk/client.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -323,14 +323,24 @@ func (c *Client) Request(ctx context.Context, method, path string, body interfac
323323
returnresp,err
324324
}
325325

326+
// ExpectJSONMime is a helper function that will assert the content type
327+
// of the response is application/json.
328+
funcExpectJSONMime(res*http.Response)error {
329+
contentType:=res.Header.Get("Content-Type")
330+
mimeType:=parseMimeType(contentType)
331+
ifmimeType!="application/json" {
332+
returnxerrors.Errorf("unexpected non-JSON response %q",contentType)
333+
}
334+
returnnil
335+
}
336+
326337
// ReadBodyAsError reads the response as a codersdk.Response, and
327338
// wraps it in a codersdk.Error type for easy marshaling.
328339
funcReadBodyAsError(res*http.Response)error {
329340
ifres==nil {
330341
returnxerrors.Errorf("no body returned")
331342
}
332343
deferres.Body.Close()
333-
contentType:=res.Header.Get("Content-Type")
334344

335345
varrequestMethod,requestURLstring
336346
ifres.Request!=nil {
@@ -352,8 +362,7 @@ func ReadBodyAsError(res *http.Response) error {
352362
returnxerrors.Errorf("read body: %w",err)
353363
}
354364

355-
mimeType:=parseMimeType(contentType)
356-
ifmimeType!="application/json" {
365+
ifmimeErr:=ExpectJSONMime(res);mimeErr!=nil {
357366
iflen(resp)>2048 {
358367
resp=append(resp[:2048], []byte("...")...)
359368
}
@@ -365,7 +374,7 @@ func ReadBodyAsError(res *http.Response) error {
365374
method:requestMethod,
366375
url:requestURL,
367376
Response:Response{
368-
Message:fmt.Sprintf("unexpected non-JSON response %q",contentType),
377+
Message:mimeErr.Error(),
369378
Detail:string(resp),
370379
},
371380
Helper:helpMessage,

‎codersdk/deployment.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2065,7 +2065,7 @@ func (c *Client) BuildInfo(ctx context.Context) (BuildInfoResponse, error) {
20652065
}
20662066
deferres.Body.Close()
20672067

2068-
ifres.StatusCode!=http.StatusOK {
2068+
ifres.StatusCode!=http.StatusOK||ExpectJSONMime(res)!=nil{
20692069
returnBuildInfoResponse{},ReadBodyAsError(res)
20702070
}
20712071

‎enterprise/wsproxy/wsproxy.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"crypto/tls"
66
"crypto/x509"
7+
"errors"
78
"fmt"
89
"net/http"
910
"net/url"
@@ -157,7 +158,10 @@ func New(ctx context.Context, opts *Options) (*Server, error) {
157158
// TODO: Probably do some version checking here
158159
info,err:=client.SDKClient.BuildInfo(ctx)
159160
iferr!=nil {
160-
returnnil,xerrors.Errorf("failed to fetch build info from %q: %w",opts.DashboardURL,err)
161+
returnnil,fmt.Errorf("buildinfo: %w",errors.Join(
162+
fmt.Errorf("unable to fetch build info from primary coderd. Are you sure %q is a coderd instance?",opts.DashboardURL),
163+
err,
164+
))
161165
}
162166
ifinfo.WorkspaceProxy {
163167
returnnil,xerrors.Errorf("%q is a workspace proxy, not a primary coderd instance",opts.DashboardURL)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp