Skip to content

Commit 763f967

Browse files
committed
Merge branch 'main' into batch-update
2 parents 07cdbc4 + cb77f04 commit 763f967

Some content is hidden

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

41 files changed

+1027
-135
lines changed

cli/errors.go

+20-1
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

+75-24
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

+13
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
9595

9696
message := uploadFlags.templateMessage(inv)
9797

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+
98110
// Confirm upload of the directory.
99111
resp, err := uploadFlags.upload(inv, client)
100112
if err != nil {
@@ -107,6 +119,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
107119
}
108120

109121
userVariableValues, err := ParseUserVariableValues(
122+
varsFiles,
110123
variablesFile,
111124
commandLineVariables)
112125
if err != nil {

cli/templateedit.go

+61-23
Original file line numberDiff line numberDiff line change
@@ -87,48 +87,86 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
8787
return xerrors.Errorf("get workspace template: %w", err)
8888
}
8989

90-
// Copy the default value if the list is empty, or if the user
91-
// specified the "none" value clear the list.
92-
if len(autostopRequirementDaysOfWeek) == 0 {
93-
autostopRequirementDaysOfWeek = template.AutostopRequirement.DaysOfWeek
90+
// Default values
91+
if !userSetOption(inv, "description") {
92+
description = template.Description
9493
}
95-
if len(autostartRequirementDaysOfWeek) == 1 && autostartRequirementDaysOfWeek[0] == "all" {
96-
// Set it to every day of the week
97-
autostartRequirementDaysOfWeek = []string{"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"}
98-
} else if len(autostartRequirementDaysOfWeek) == 0 {
99-
autostartRequirementDaysOfWeek = template.AutostartRequirement.DaysOfWeek
94+
95+
if !userSetOption(inv, "icon") {
96+
icon = template.Icon
10097
}
101-
if unsetAutostopRequirementDaysOfWeek {
102-
autostopRequirementDaysOfWeek = []string{}
98+
99+
if !userSetOption(inv, "display-name") {
100+
displayName = template.DisplayName
103101
}
104-
if failureTTL == 0 {
102+
103+
if !userSetOption(inv, "max-ttl") {
104+
maxTTL = time.Duration(template.MaxTTLMillis) * time.Millisecond
105+
}
106+
107+
if !userSetOption(inv, "default-ttl") {
108+
defaultTTL = time.Duration(template.DefaultTTLMillis) * time.Millisecond
109+
}
110+
111+
if !userSetOption(inv, "allow-user-autostop") {
112+
allowUserAutostop = template.AllowUserAutostop
113+
}
114+
115+
if !userSetOption(inv, "allow-user-autostart") {
116+
allowUserAutostart = template.AllowUserAutostart
117+
}
118+
119+
if !userSetOption(inv, "allow-user-cancel-workspace-jobs") {
120+
allowUserCancelWorkspaceJobs = template.AllowUserCancelWorkspaceJobs
121+
}
122+
123+
if !userSetOption(inv, "failure-ttl") {
105124
failureTTL = time.Duration(template.FailureTTLMillis) * time.Millisecond
106125
}
107-
if dormancyThreshold == 0 {
126+
127+
if !userSetOption(inv, "dormancy-threshold") {
108128
dormancyThreshold = time.Duration(template.TimeTilDormantMillis) * time.Millisecond
109129
}
110-
if dormancyAutoDeletion == 0 {
130+
131+
if !userSetOption(inv, "dormancy-auto-deletion") {
111132
dormancyAutoDeletion = time.Duration(template.TimeTilDormantAutoDeleteMillis) * time.Millisecond
112133
}
113134

114-
// Default values
115-
if !userSetOption(inv, "description") {
116-
description = template.Description
135+
if !userSetOption(inv, "require-active-version") {
136+
requireActiveVersion = template.RequireActiveVersion
117137
}
118138

119-
if !userSetOption(inv, "icon") {
120-
icon = template.Icon
139+
if !userSetOption(inv, "autostop-requirement-weekdays") {
140+
autostopRequirementDaysOfWeek = template.AutostopRequirement.DaysOfWeek
121141
}
122142

123-
if !userSetOption(inv, "display-name") {
124-
displayName = template.DisplayName
143+
if unsetAutostopRequirementDaysOfWeek {
144+
autostopRequirementDaysOfWeek = []string{}
145+
}
146+
147+
if !userSetOption(inv, "autostop-requirement-weeks") {
148+
autostopRequirementWeeks = template.AutostopRequirement.Weeks
149+
}
150+
151+
if len(autostartRequirementDaysOfWeek) == 1 && autostartRequirementDaysOfWeek[0] == "all" {
152+
// Set it to every day of the week
153+
autostartRequirementDaysOfWeek = []string{"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"}
154+
} else if !userSetOption(inv, "autostart-requirement-weekdays") {
155+
autostartRequirementDaysOfWeek = template.AutostartRequirement.DaysOfWeek
156+
} else if len(autostartRequirementDaysOfWeek) == 0 {
157+
autostartRequirementDaysOfWeek = []string{}
125158
}
126159

127160
var deprecated *string
128-
if !userSetOption(inv, "deprecated") {
161+
if userSetOption(inv, "deprecated") {
129162
deprecated = &deprecationMessage
130163
}
131164

165+
var disableEveryoneGroup bool
166+
if userSetOption(inv, "private") {
167+
disableEveryoneGroup = disableEveryone
168+
}
169+
132170
req := codersdk.UpdateTemplateMeta{
133171
Name: name,
134172
DisplayName: displayName,
@@ -151,7 +189,7 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
151189
AllowUserAutostop: allowUserAutostop,
152190
RequireActiveVersion: requireActiveVersion,
153191
DeprecationMessage: deprecated,
154-
DisableEveryoneGroupAccess: disableEveryone,
192+
DisableEveryoneGroupAccess: disableEveryoneGroup,
155193
}
156194

157195
_, err = client.UpdateTemplateMeta(inv.Context(), template.ID, req)

cli/templatepush.go

+13
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ func (r *RootCmd) templatePush() *clibase.Cmd {
7878

7979
message := uploadFlags.templateMessage(inv)
8080

81+
var varsFiles []string
82+
if !uploadFlags.stdin() {
83+
varsFiles, err = DiscoverVarsFiles(uploadFlags.directory)
84+
if err != nil {
85+
return err
86+
}
87+
88+
if len(varsFiles) > 0 {
89+
_, _ = fmt.Fprintln(inv.Stdout, "Auto-discovered Terraform tfvars files. Make sure to review and clean up any unused files.")
90+
}
91+
}
92+
8193
resp, err := uploadFlags.upload(inv, client)
8294
if err != nil {
8395
return err
@@ -89,6 +101,7 @@ func (r *RootCmd) templatePush() *clibase.Cmd {
89101
}
90102

91103
userVariableValues, err := ParseUserVariableValues(
104+
varsFiles,
92105
variablesFile,
93106
commandLineVariables)
94107
if err != nil {

0 commit comments

Comments
 (0)