Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Handle wac template parse errors #271

Merged
merged 9 commits into from
Mar 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
41 changes: 31 additions & 10 deletions coder-sdk/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,52 @@ var ErrPermissions = xerrors.New("insufficient permissions")
// ErrAuthentication describes the error case in which the requester has invalid authentication.
var ErrAuthentication = xerrors.New("invalid authentication")

// apiError is the expected payload format for our errors.
type apiError struct {
Err apiErrorMsg `json:"error"`
// APIError is the expected payload format for API errors.
type APIError struct {
Err APIErrorMsg `json:"error"`
}

// apiErrorMsg contains the rich error information returned by API errors.
type apiErrorMsg struct {
Msg string `json:"msg"`
// APIErrorMsg contains the rich error information returned by API errors.
type APIErrorMsg struct {
Msg string `json:"msg"`
Code string `json:"code"`
Details json.RawMessage `json:"details"`
}

// HTTPError represents an error from the Coder API.
type HTTPError struct {
*http.Response
cached *APIError
cachedErr error
Comment on lines +35 to +36
Copy link
Member Author

Choose a reason for hiding this comment

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

We decode the re.Response.Body on .Error(). I didn't feel like trying to fix that behavior, but I need to cache the decoded payload as .Error() can be called more than once, and then the second one fails. So I split .Error() in to .Payload so I can extract the payload and not change any current behavior.

Copy link
Contributor

Choose a reason for hiding this comment

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

oh nice catch, I also think we had this buggy code duplicated in the monorepo. merged a patch there last week.

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 still feels a little odd, but I didn't want to refactor how errors worked as I only touched small areas of the cli so far.
So this works for now.

}

func (e *HTTPError) Error() string {
var msg apiError
// Payload decode the response body into the standard error structure. The `details`
// section is stored as a raw json, and type depends on the `code` field.
func (e *HTTPError) Payload() (*APIError, error) {
var msg APIError
if e.cached != nil || e.cachedErr != nil {
return e.cached, e.cachedErr
}

// Try to decode the payload as an error, if it fails or if there is no error message,
// return the response URL with the status.
if err := json.NewDecoder(e.Response.Body).Decode(&msg); err != nil || msg.Err.Msg == "" {
if err := json.NewDecoder(e.Response.Body).Decode(&msg); err != nil {
e.cachedErr = err
return nil, err
}

e.cached = &msg
return &msg, nil
}

func (e *HTTPError) Error() string {
apiErr, err := e.Payload()
if err != nil || apiErr.Err.Msg == "" {
return fmt.Sprintf("%s: %d %s", e.Request.URL, e.StatusCode, e.Status)
}

// If the payload was a in the expected error format with a message, include it.
return msg.Err.Msg
return apiErr.Err.Msg
}

func bodyError(resp *http.Response) error {
Expand Down
8 changes: 4 additions & 4 deletions internal/cmd/envs.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,8 @@ func createEnvFromRepoCmd() *cobra.Command {
Long: "Create a new Coder environment from a config file.",
Hidden: true,
Example: `# create a new environment from git repository template
coder envs create-from-repo --repo-url github.com/cdr/m --branch my-branch
coder envs create-from-repo -f coder.yaml`,
coder envs create-from-config --repo-url github.com/cdr/m --branch my-branch
coder envs create-from-config -f coder.yaml`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

Expand Down Expand Up @@ -355,7 +355,7 @@ coder envs create-from-repo -f coder.yaml`,

tpl, err := client.ParseTemplate(ctx, req)
if err != nil {
return xerrors.Errorf("parse environment template config: %w", err)
return handleAPIError(err)
}

provider, err := coderutil.DefaultWorkspaceProvider(ctx, client)
Expand All @@ -370,7 +370,7 @@ coder envs create-from-repo -f coder.yaml`,
Namespace: provider.DefaultNamespace,
})
if err != nil {
return xerrors.Errorf("create environment: %w", err)
return handleAPIError(err)
}

if follow {
Expand Down
71 changes: 71 additions & 0 deletions internal/cmd/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cmd

import (
"encoding/json"
"fmt"

"golang.org/x/xerrors"

"cdr.dev/coder-cli/coder-sdk"
"cdr.dev/coder-cli/pkg/clog"
)

// handleAPIError attempts to convert an api error into a more detailed clog error.
// If it cannot, it will return the original error.
func handleAPIError(origError error) error {
var httpError *coder.HTTPError
if !xerrors.As(origError, &httpError) {
return origError // Return the original
}

ae, err := httpError.Payload()
if err != nil {
return origError // Return the original
}

switch ae.Err.Code {
case "wac_template": // template parse errors
type templatePayload struct {
ErrorType string `json:"error_type"`
Msgs []string `json:"messages"`
}

var p templatePayload
err := json.Unmarshal(ae.Err.Details, &p)
if err != nil {
return origError
}

return clog.Error(p.ErrorType, p.Msgs...)
case "verbose":
type verbosePayload struct {
Verbose string `json:"verbose"`
}
var p verbosePayload
err := json.Unmarshal(ae.Err.Details, &p)
if err != nil {
return origError
}

return clog.Error(origError.Error(), p.Verbose)
case "precondition":
type preconditionPayload struct {
Error string `json:"error"`
Message string `json:"message"`
Solution string `json:"solution"`
}

var p preconditionPayload
err := json.Unmarshal(ae.Err.Details, &p)
if err != nil {
return origError
}

return clog.Error(fmt.Sprintf("Precondition Error : Status Code=%d", httpError.StatusCode),
p.Message,
clog.BlankLine,
clog.Tipf(p.Solution))
}

return origError // Return the original
}