Skip to content

Commit 6d3148b

Browse files
committed
Merge branch 'main' of https://github.com/coder/coder into bq/move-alert
2 parents 9299c68 + aeb1ab8 commit 6d3148b

File tree

89 files changed

+1132
-372
lines changed

Some content is hidden

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

89 files changed

+1132
-372
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/templateedit.go

Lines changed: 61 additions & 23 deletions
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)

coderd/apidoc/docs.go

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbgen/dbgen.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ func Template(t testing.TB, db database.Store, seed database.Template) database.
7272
seed.OrganizationID.String(): []rbac.Action{rbac.ActionRead},
7373
}
7474
}
75+
if seed.UserACL == nil {
76+
seed.UserACL = database.TemplateACL{}
77+
}
7578
err := db.InsertTemplate(genCtx, database.InsertTemplateParams{
7679
ID: id,
7780
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),

coderd/database/dbmem/dbmem.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6374,6 +6374,7 @@ func (q *FakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.Upd
63746374
tpl.Description = arg.Description
63756375
tpl.Icon = arg.Icon
63766376
tpl.GroupACL = arg.GroupACL
6377+
tpl.AllowUserCancelWorkspaceJobs = arg.AllowUserCancelWorkspaceJobs
63776378
q.templates[idx] = tpl
63786379
return nil
63796380
}

coderd/externalauth/externalauth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ func (c *DeviceAuth) AuthorizeDevice(ctx context.Context) (*codersdk.ExternalAut
325325
// return a better error.
326326
switch resp.StatusCode {
327327
case http.StatusTooManyRequests:
328-
return nil, fmt.Errorf("rate limit hit, unable to authorize device. please try again later")
328+
return nil, xerrors.New("rate limit hit, unable to authorize device. please try again later")
329329
default:
330330
return nil, err
331331
}

0 commit comments

Comments
 (0)