Skip to content

Commit 965c37a

Browse files
committed
Merge branch 'main' into new-group-errors
2 parents af61635 + 9052920 commit 965c37a

File tree

139 files changed

+3959
-1091
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

139 files changed

+3959
-1091
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
var magicWord 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+
return xerrors.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
if f, ok := inv.Stdin.(*os.File); ok {
7288
_ = f.Close()
7389
}
7490

75-
return xerrors.Errorf("some error: %w", errorWithStackTrace())
91+
return errors.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(err error) {
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
const indent = " "
10311031

10321032
// cliHumanFormatError formats an error for the CLI. Newlines and styling are
1033-
// included.
1034-
func cliHumanFormatError(err error, 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+
func cliHumanFormatError(from string, err error, opts *formatOpts) (string, bool) {
10351047
if opts == nil {
10361048
opts = &formatOpts{}
10371049
}
1050+
if err == nil {
1051+
return "<nil>", true
1052+
}
10381053

1039-
//nolint:errorlint
10401054
if multi, ok := err.(interface{ Unwrap() []error }); ok {
10411055
multiErrors := multi.Unwrap()
10421056
if len(multiErrors) == 1 {
10431057
// Format as a single error
1044-
return cliHumanFormatError(multiErrors[0], opts)
1058+
return cliHumanFormatError(from, multiErrors[0], opts)
10451059
}
1046-
return formatMultiError(multiErrors, opts)
1060+
return formatMultiError(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-
var sdkError *codersdk.Error
1052-
if errors.As(err, &sdkError) {
1053-
return formatCoderSDKError(sdkError, opts)
1065+
if sdkError, ok := err.(*codersdk.Error); ok {
1066+
return formatCoderSDKError(from, sdkError, opts), true
10541067
}
10551068

1056-
var cmdErr *clibase.RunCommandError
1057-
if errors.As(err, &cmdErr) {
1058-
return formatRunCommandError(cmdErr, opts)
1069+
if cmdErr, 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+
return formatRunCommandError(cmdErr, opts), true
10591073
}
10601074

1075+
uw, ok := err.(interface{ Unwrap() error })
1076+
if ok {
1077+
msg, special := cliHumanFormatError(from+traceError(err), uw.Unwrap(), opts)
1078+
if special {
1079+
return msg, 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
if opts.Verbose {
1064-
return pretty.Sprint(headLineStyle(), fmt.Sprintf("%+v", err))
1089+
return pretty.Sprint(headLineStyle(), fmt.Sprintf("%+v", err)), false
10651090
}
10661091

1067-
return pretty.Sprint(headLineStyle(), fmt.Sprintf("%v", err))
1092+
return pretty.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-
func formatMultiError(multi []error, opts *formatOpts) string {
1103+
func formatMultiError(from string, multi []error, opts *formatOpts) string {
10791104
var errorStrings []string
10801105
for _, err := range multi {
1081-
errorStrings = append(errorStrings, cliHumanFormatError(err, opts))
1106+
msg, _ := cliHumanFormatError("", err, opts)
1107+
errorStrings = append(errorStrings, msg)
10821108
}
10831109

10841110
// Write errors out
10851111
var str strings.Builder
1086-
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("%d errors encountered:", len(multi))))
1112+
var traceMsg string
1113+
if from != "" {
1114+
traceMsg = fmt.Sprintf("Trace=[%s])", from)
1115+
}
1116+
_, _ = str.WriteString(pretty.Sprint(headLineStyle(), fmt.Sprintf("%d errors encountered: %s", len(multi), traceMsg)))
10871117
for i, errStr := range errorStrings {
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
var str 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-
if opts.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+
if special {
1148+
_, _ = str.WriteString(msgString)
1149+
} else {
1150+
_, _ = str.WriteString(pretty.Sprint(tailLineStyle(), msgString))
1151+
}
1152+
11221153
return str.String()
11231154
}
11241155

11251156
// formatCoderSDKError come from API requests. In verbose mode, add the
11261157
// request debug information.
1127-
func formatCoderSDKError(err *codersdk.Error, opts *formatOpts) string {
1158+
func formatCoderSDKError(from string, err *codersdk.Error, opts *formatOpts) string {
11281159
var str strings.Builder
11291160
if opts.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+
if from != "" {
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
if err.Helper != "" {
@@ -1144,6 +1180,21 @@ func formatCoderSDKError(err *codersdk.Error, opts *formatOpts) string {
11441180
return str.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+
func traceError(err error) string {
1190+
if uw, ok := err.(interface{ Unwrap() error }); ok {
1191+
a, b := err.Error(), uw.Unwrap().Error()
1192+
c := strings.TrimSuffix(a, b)
1193+
return c
1194+
}
1195+
return err.Error()
1196+
}
1197+
11471198
// These styles are arbitrary.
11481199
func headLineStyle() pretty.Style {
11491200
return cliui.DefaultStyles.Error

cli/templatecreate.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,6 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
4949
isTemplateSchedulingOptionsSet := failureTTL != 0 || dormancyThreshold != 0 || dormancyAutoDeletion != 0 || maxTTL != 0
5050

5151
if isTemplateSchedulingOptionsSet || requireActiveVersion {
52-
if failureTTL != 0 || dormancyThreshold != 0 || dormancyAutoDeletion != 0 {
53-
// This call can be removed when workspace_actions is no longer experimental
54-
experiments, exErr := client.Experiments(inv.Context())
55-
if exErr != nil {
56-
return xerrors.Errorf("get experiments: %w", exErr)
57-
}
58-
59-
if !experiments.Enabled(codersdk.ExperimentWorkspaceActions) {
60-
return xerrors.Errorf("--failure-ttl, --dormancy-threshold, and --dormancy-auto-deletion are experimental features. Use the workspace_actions CODER_EXPERIMENTS flag to set these configuration values.")
61-
}
62-
}
63-
6452
entitlements, err := client.Entitlements(inv.Context())
6553
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusNotFound {
6654
return xerrors.Errorf("your deployment appears to be an AGPL deployment, so you cannot set enterprise-only flags")
@@ -107,6 +95,18 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
10795

10896
message := uploadFlags.templateMessage(inv)
10997

98+
var varsFiles []string
99+
if !uploadFlags.stdin() {
100+
varsFiles, err = DiscoverVarsFiles(uploadFlags.directory)
101+
if err != nil {
102+
return err
103+
}
104+
105+
if len(varsFiles) > 0 {
106+
_, _ = fmt.Fprintln(inv.Stdout, "Auto-discovered Terraform tfvars files. Make sure to review and clean up any unused files.")
107+
}
108+
}
109+
110110
// Confirm upload of the directory.
111111
resp, err := uploadFlags.upload(inv, client)
112112
if err != nil {
@@ -119,6 +119,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
119119
}
120120

121121
userVariableValues, err := ParseUserVariableValues(
122+
varsFiles,
122123
variablesFile,
123124
commandLineVariables)
124125
if err != nil {

0 commit comments

Comments
 (0)