Skip to content

Commit e857c5b

Browse files
authored
feat: Improved handling of unexpected errors (#190)
* feat: Improved handling of unexpected errors This happens e.g. when a wrong API endpoint is set and the error cannot be parsed by the OpenAPI client. In this case we want to at least output the requested URL and the response code. * chore(docs): Auto-update docs and licenses Signed-off-by: NautiluX <2600004+NautiluX@users.noreply.github.com> --------- Signed-off-by: NautiluX <2600004+NautiluX@users.noreply.github.com> Co-authored-by: NautiluX <2600004+NautiluX@users.noreply.github.com>
1 parent c3cbbde commit e857c5b

File tree

9 files changed

+97
-75
lines changed

9 files changed

+97
-75
lines changed

api/client.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,46 +78,46 @@ func NewHttpClient() *http.Client {
7878
}
7979

8080
func (c *Client) ListDataCenters() ([]DataCenter, error) {
81-
datacenters, _, err := c.api.MetadataAPI.MetadataGetDatacenters(c.ctx).Execute()
82-
return datacenters, errors.FormatAPIError(err)
81+
datacenters, r, err := c.api.MetadataAPI.MetadataGetDatacenters(c.ctx).Execute()
82+
return datacenters, errors.FormatAPIError(r, err)
8383
}
8484

8585
func (c *Client) ListDomains(teamId int) ([]Domain, error) {
86-
domains, _, err := c.api.DomainsAPI.DomainsListDomains(c.ctx, float32(teamId)).Execute()
87-
return domains, errors.FormatAPIError(err)
86+
domains, r, err := c.api.DomainsAPI.DomainsListDomains(c.ctx, float32(teamId)).Execute()
87+
return domains, errors.FormatAPIError(r, err)
8888
}
8989

9090
func (c *Client) GetDomain(teamId int, domainName string) (*Domain, error) {
91-
domain, _, err := c.api.DomainsAPI.DomainsGetDomain(c.ctx, float32(teamId), domainName).Execute()
92-
return domain, errors.FormatAPIError(err)
91+
domain, r, err := c.api.DomainsAPI.DomainsGetDomain(c.ctx, float32(teamId), domainName).Execute()
92+
return domain, errors.FormatAPIError(r, err)
9393
}
9494

9595
func (c *Client) CreateDomain(teamId int, domainName string) (*Domain, error) {
96-
domain, _, err := c.api.DomainsAPI.DomainsCreateDomain(c.ctx, float32(teamId), domainName).Execute()
97-
return domain, errors.FormatAPIError(err)
96+
domain, r, err := c.api.DomainsAPI.DomainsCreateDomain(c.ctx, float32(teamId), domainName).Execute()
97+
return domain, errors.FormatAPIError(r, err)
9898
}
9999

100100
func (c *Client) DeleteDomain(teamId int, domainName string) error {
101-
_, err := c.api.DomainsAPI.DomainsDeleteDomain(c.ctx, float32(teamId), domainName).Execute()
102-
return errors.FormatAPIError(err)
101+
r, err := c.api.DomainsAPI.DomainsDeleteDomain(c.ctx, float32(teamId), domainName).Execute()
102+
return errors.FormatAPIError(r, err)
103103
}
104104

105105
func (c *Client) UpdateDomain(
106106
teamId int, domainName string, args UpdateDomainArgs,
107107
) (*Domain, error) {
108-
domain, _, err := c.api.DomainsAPI.
108+
domain, r, err := c.api.DomainsAPI.
109109
DomainsUpdateDomain(c.ctx, float32(teamId), domainName).
110110
DomainsUpdateDomainRequest(args).
111111
Execute()
112-
return domain, errors.FormatAPIError(err)
112+
return domain, errors.FormatAPIError(r, err)
113113
}
114114

115115
func (c *Client) VerifyDomain(
116116
teamId int, domainName string,
117117
) (*DomainVerificationStatus, error) {
118-
status, _, err := c.api.DomainsAPI.
118+
status, r, err := c.api.DomainsAPI.
119119
DomainsVerifyDomain(c.ctx, float32(teamId), domainName).Execute()
120-
return status, errors.FormatAPIError(err)
120+
return status, errors.FormatAPIError(r, err)
121121
}
122122

123123
func (c *Client) UpdateWorkspaceConnections(
@@ -131,13 +131,13 @@ func (c *Client) UpdateWorkspaceConnections(
131131
}
132132
req[path] = ids
133133
}
134-
domain, _, err := c.api.DomainsAPI.
134+
domain, r, err := c.api.DomainsAPI.
135135
DomainsUpdateWorkspaceConnections(c.ctx, float32(teamId), domainName).
136136
RequestBody(req).Execute()
137-
return domain, errors.FormatAPIError(err)
137+
return domain, errors.FormatAPIError(r, err)
138138
}
139139

140140
func (c *Client) ListBaseimages() ([]Baseimage, error) {
141-
baseimages, _, err := c.api.MetadataAPI.MetadataGetWorkspaceBaseImages(c.ctx).Execute()
142-
return baseimages, errors.FormatAPIError(err)
141+
baseimages, r, err := c.api.MetadataAPI.MetadataGetWorkspaceBaseImages(c.ctx).Execute()
142+
return baseimages, errors.FormatAPIError(r, err)
143143
}

api/errors/errors.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package errors
66
import (
77
"encoding/json"
88
"fmt"
9+
"net/http"
10+
"net/url"
911
"time"
1012

1113
"github.com/codesphere-cloud/cs-go/api/openapi_client"
@@ -60,24 +62,33 @@ type APIErrorResponse struct {
6062
TraceId string `json:"traceId"`
6163
}
6264

63-
func FormatAPIError(err error) error {
65+
func FormatAPIError(r *http.Response, err error) error {
6466
if err == nil {
6567
return nil
6668
}
6769

70+
if r == nil {
71+
r = &http.Response{
72+
StatusCode: -1,
73+
}
74+
}
75+
if r.Request == nil {
76+
r.Request = &http.Request{URL: &url.URL{}}
77+
}
78+
6879
openAPIErr, ok := err.(*openapi_client.GenericOpenAPIError)
6980
if !ok {
70-
return err
81+
return fmt.Errorf("unexpected error %d at URL %s: %w", r.StatusCode, r.Request.URL, err)
7182
}
7283

7384
body := openAPIErr.Body()
7485
if len(body) == 0 {
75-
return err
86+
return fmt.Errorf("unexpected error %d at URL %s: %w", r.StatusCode, r.Request.URL, err)
7687
}
7788

7889
var apiErr APIErrorResponse
7990
if json.Unmarshal(body, &apiErr) != nil {
80-
return err
91+
return fmt.Errorf("unexpected error %d at URL %s: %w", r.StatusCode, r.Request.URL, err)
8192
}
8293

8394
return fmt.Errorf("codesphere API returned error %d (%s): %s", apiErr.Status, apiErr.Title, apiErr.Detail)

api/errors/errors_test.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package errors_test
55

66
import (
77
"fmt"
8+
"net/http"
9+
"net/url"
810
"reflect"
911
"unsafe"
1012

@@ -29,20 +31,30 @@ func makeGenericOpenAPIError(body []byte, errStr string) error {
2931
}
3032

3133
var _ = Describe("FormatAPIError", func() {
34+
var (
35+
r *http.Response
36+
)
37+
38+
BeforeEach(func() {
39+
r = &http.Response{
40+
StatusCode: 123,
41+
Request: &http.Request{URL: &url.URL{Scheme: "http", Host: "codesphere.com", Path: "/api/fake"}},
42+
}
43+
})
3244
It("returns nil for nil error", func() {
33-
Expect(errors.FormatAPIError(nil)).To(BeNil())
45+
Expect(errors.FormatAPIError(nil, nil)).To(BeNil())
3446
})
3547

3648
It("returns regular error unchanged", func() {
3749
err := fmt.Errorf("regular error")
38-
res := errors.FormatAPIError(err)
50+
res := errors.FormatAPIError(r, err)
3951
Expect(res).ToNot(BeNil())
40-
Expect(res.Error()).To(Equal("regular error"))
52+
Expect(res.Error()).To(Equal(fmt.Sprintf("unexpected error %d at URL %s: %s", r.StatusCode, r.Request.URL.String(), err.Error())))
4153
})
4254

4355
It("parses API JSON error and formats it", func() {
4456
apiErr := makeGenericOpenAPIError([]byte(`{"status":400,"title":"Workspace is not running","detail":"Workspace '796636' is not in a running state.","traceId":"svJDMa5"}`), "400 Bad Request")
45-
res := errors.FormatAPIError(apiErr)
57+
res := errors.FormatAPIError(r, apiErr)
4658
Expect(res).ToNot(BeNil())
4759
Expect(res.Error()).To(Equal("API error 400 Workspace is not running: Workspace '796636' is not in a running state."))
4860
})

api/plan.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ func (client *Client) PlanByName(name string) (WorkspacePlan, error) {
2727
}
2828

2929
func (c *Client) ListWorkspacePlans() ([]WorkspacePlan, error) {
30-
plans, _, err := c.api.MetadataAPI.MetadataGetWorkspacePlans(c.ctx).Execute()
31-
return plans, cserrors.FormatAPIError(err)
30+
plans, r, err := c.api.MetadataAPI.MetadataGetWorkspacePlans(c.ctx).Execute()
31+
return plans, cserrors.FormatAPIError(r, err)
3232
}

api/team.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,26 @@ func (client *Client) TeamIdByName(name string) (Team, error) {
4040
}
4141

4242
func (c *Client) ListTeams() ([]Team, error) {
43-
teams, _, err := c.api.TeamsAPI.TeamsListTeams(c.ctx).Execute()
44-
return teams, cserrors.FormatAPIError(err)
43+
teams, r, err := c.api.TeamsAPI.TeamsListTeams(c.ctx).Execute()
44+
return teams, cserrors.FormatAPIError(r, err)
4545
}
4646

4747
func (c *Client) GetTeam(teamId int) (*Team, error) {
48-
team, _, err := c.api.TeamsAPI.TeamsGetTeam(c.ctx, float32(teamId)).Execute()
49-
return ConvertToTeam(team), cserrors.FormatAPIError(err)
48+
team, r, err := c.api.TeamsAPI.TeamsGetTeam(c.ctx, float32(teamId)).Execute()
49+
return ConvertToTeam(team), cserrors.FormatAPIError(r, err)
5050
}
5151

5252
func (c *Client) CreateTeam(name string, dc int) (*Team, error) {
53-
team, _, err := c.api.TeamsAPI.TeamsCreateTeam(c.ctx).
53+
team, r, err := c.api.TeamsAPI.TeamsCreateTeam(c.ctx).
5454
TeamsCreateTeamRequest(openapi_client.TeamsCreateTeamRequest{
5555
Name: name,
5656
Dc: dc,
5757
}).
5858
Execute()
59-
return ConvertToTeam(team), cserrors.FormatAPIError(err)
59+
return ConvertToTeam(team), cserrors.FormatAPIError(r, err)
6060
}
6161

6262
func (c *Client) DeleteTeam(teamId int) error {
63-
_, err := c.api.TeamsAPI.TeamsDeleteTeam(c.ctx, float32(teamId)).Execute()
64-
return cserrors.FormatAPIError(err)
63+
r, err := c.api.TeamsAPI.TeamsDeleteTeam(c.ctx, float32(teamId)).Execute()
64+
return cserrors.FormatAPIError(r, err)
6565
}

api/workspace.go

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,32 @@ import (
1313
)
1414

1515
func (c *Client) ListWorkspaces(teamId int) ([]Workspace, error) {
16-
workspaces, _, err := c.api.WorkspacesAPI.WorkspacesListWorkspaces(c.ctx, float32(teamId)).Execute()
17-
return workspaces, errors.FormatAPIError(err)
16+
workspaces, r, err := c.api.WorkspacesAPI.WorkspacesListWorkspaces(c.ctx, float32(teamId)).Execute()
17+
return workspaces, errors.FormatAPIError(r, err)
1818
}
1919

2020
func (c *Client) GetWorkspace(workspaceId int) (Workspace, error) {
21-
workspace, _, err := c.api.WorkspacesAPI.WorkspacesGetWorkspace(c.ctx, float32(workspaceId)).Execute()
21+
workspace, r, err := c.api.WorkspacesAPI.WorkspacesGetWorkspace(c.ctx, float32(workspaceId)).Execute()
2222

2323
if workspace != nil {
24-
return *workspace, errors.FormatAPIError(err)
24+
return *workspace, errors.FormatAPIError(r, err)
2525
}
26-
return Workspace{}, errors.FormatAPIError(err)
26+
return Workspace{}, errors.FormatAPIError(r, err)
2727
}
2828

2929
func (c *Client) DeleteWorkspace(workspaceId int) error {
30-
_, err := c.api.WorkspacesAPI.WorkspacesDeleteWorkspace(c.ctx, float32(workspaceId)).Execute()
31-
return errors.FormatAPIError(err)
30+
r, err := c.api.WorkspacesAPI.WorkspacesDeleteWorkspace(c.ctx, float32(workspaceId)).Execute()
31+
return errors.FormatAPIError(r, err)
3232
}
3333

3434
func (c *Client) WorkspaceStatus(workspaceId int) (*WorkspaceStatus, error) {
35-
status, _, err := c.api.WorkspacesAPI.WorkspacesGetWorkspaceStatus(c.ctx, float32(workspaceId)).Execute()
36-
return status, errors.FormatAPIError(err)
35+
status, r, err := c.api.WorkspacesAPI.WorkspacesGetWorkspaceStatus(c.ctx, float32(workspaceId)).Execute()
36+
return status, errors.FormatAPIError(r, err)
3737
}
3838

3939
func (c *Client) CreateWorkspace(args CreateWorkspaceArgs) (*Workspace, error) {
40-
workspace, _, err := c.api.WorkspacesAPI.WorkspacesCreateWorkspace(c.ctx).WorkspacesCreateWorkspaceRequest(args).Execute()
41-
return workspace, errors.FormatAPIError(err)
40+
workspace, r, err := c.api.WorkspacesAPI.WorkspacesCreateWorkspace(c.ctx).WorkspacesCreateWorkspaceRequest(args).Execute()
41+
return workspace, errors.FormatAPIError(r, err)
4242
}
4343

4444
func (c *Client) SetEnvVarOnWorkspace(workspaceId int, envVars map[string]string) error {
@@ -52,8 +52,8 @@ func (c *Client) SetEnvVarOnWorkspace(workspaceId int, envVars map[string]string
5252

5353
req := c.api.WorkspacesAPI.WorkspacesSetEnvVar(c.ctx, float32(workspaceId)).
5454
WorkspacesCreateWorkspaceRequestEnvInner(vars)
55-
_, err := c.api.WorkspacesAPI.WorkspacesSetEnvVarExecute(req)
56-
return errors.FormatAPIError(err)
55+
r, err := c.api.WorkspacesAPI.WorkspacesSetEnvVarExecute(req)
56+
return errors.FormatAPIError(r, err)
5757
}
5858

5959
func (c *Client) ExecCommand(workspaceId int, command string, workdir string, env map[string]string) (string, string, error) {
@@ -69,43 +69,43 @@ func (c *Client) ExecCommand(workspaceId int, command string, workdir string, en
6969
}
7070

7171
req := c.api.WorkspacesAPI.WorkspacesExecuteCommand(c.ctx, float32(workspaceId)).WorkspacesExecuteCommandRequest(cmd)
72-
res, _, err := req.Execute()
72+
res, r, err := req.Execute()
7373

7474
if err != nil {
75-
return "", "", errors.FormatAPIError(err)
75+
return "", "", errors.FormatAPIError(r, err)
7676
}
7777
if res == nil {
78-
return "", "", errors.FormatAPIError(err)
78+
return "", "", errors.FormatAPIError(r, err)
7979
}
80-
return res.Output, res.Error, errors.FormatAPIError(err)
80+
return res.Output, res.Error, errors.FormatAPIError(r, err)
8181
}
8282

8383
func (c *Client) DeployLandscape(wsId int, profile string) error {
8484
if profile == "ci.yml" || profile == "" {
8585
req := c.api.WorkspacesAPI.WorkspacesDeployLandscape(c.ctx, float32(wsId))
86-
_, err := req.Execute()
87-
return errors.FormatAPIError(err)
86+
r, err := req.Execute()
87+
return errors.FormatAPIError(r, err)
8888
}
8989
req := c.api.WorkspacesAPI.WorkspacesDeployLandscape1(c.ctx, float32(wsId), profile)
90-
_, err := req.Execute()
91-
return errors.FormatAPIError(err)
90+
r, err := req.Execute()
91+
return errors.FormatAPIError(r, err)
9292
}
9393

9494
func (c *Client) StartPipelineStage(wsId int, profile string, stage string) error {
9595
if profile == "ci.yml" || profile == "" {
9696
req := c.api.WorkspacesAPI.WorkspacesStartPipelineStage(c.ctx, float32(wsId), stage)
97-
_, err := req.Execute()
98-
return errors.FormatAPIError(err)
97+
r, err := req.Execute()
98+
return errors.FormatAPIError(r, err)
9999
}
100100
req := c.api.WorkspacesAPI.WorkspacesStartPipelineStage1(c.ctx, float32(wsId), stage, profile)
101-
_, err := req.Execute()
102-
return errors.FormatAPIError(err)
101+
r, err := req.Execute()
102+
return errors.FormatAPIError(r, err)
103103
}
104104

105105
func (c *Client) GetPipelineState(wsId int, stage string) ([]PipelineStatus, error) {
106106
req := c.api.WorkspacesAPI.WorkspacesPipelineStatus(c.ctx, float32(wsId), stage)
107-
res, _, err := req.Execute()
108-
return res, errors.FormatAPIError(err)
107+
res, r, err := req.Execute()
108+
return res, errors.FormatAPIError(r, err)
109109
}
110110

111111
// ScaleWorkspace sets the number of replicas for a workspace.
@@ -115,8 +115,8 @@ func (c *Client) ScaleWorkspace(wsId int, replicas int) error {
115115
WorkspacesUpdateWorkspaceRequest(openapi_client.WorkspacesUpdateWorkspaceRequest{
116116
Replicas: &replicas,
117117
})
118-
_, err := req.Execute()
119-
return errors.FormatAPIError(err)
118+
r, err := req.Execute()
119+
return errors.FormatAPIError(r, err)
120120
}
121121

122122
// Waits for a given workspace to be running.
@@ -131,7 +131,7 @@ func (client *Client) WaitForWorkspaceRunning(workspace *Workspace, timeout time
131131

132132
if err != nil {
133133
if client.time.Now().After(maxWaitTime) {
134-
return errors.FormatAPIError(err)
134+
return err
135135
}
136136
client.time.Sleep(delay)
137137
continue
@@ -202,11 +202,11 @@ func (client Client) DeployWorkspace(args DeployWorkspaceArgs) (*Workspace, erro
202202
func (c Client) GitPull(workspaceId int, remote string, branch string) error {
203203
if remote == "" {
204204
req := c.api.WorkspacesAPI.WorkspacesGitPull(c.ctx, float32(workspaceId))
205-
_, err := req.Execute()
206-
return errors.FormatAPIError(err)
205+
r, err := req.Execute()
206+
return errors.FormatAPIError(r, err)
207207
}
208208

209209
req := c.api.WorkspacesAPI.WorkspacesGitPull2(c.ctx, float32(workspaceId), remote, branch)
210-
_, err := req.Execute()
211-
return errors.FormatAPIError(err)
210+
r, err := req.Execute()
211+
return errors.FormatAPIError(r, err)
212212
}

cli/cmd/exec.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"os"
1010
"strings"
1111

12-
"github.com/codesphere-cloud/cs-go/api/errors"
1312
"github.com/codesphere-cloud/cs-go/pkg/cs"
1413
"github.com/codesphere-cloud/cs-go/pkg/io"
1514

@@ -81,5 +80,5 @@ func (c *ExecCmd) ExecCommand(client Client, command string) error {
8180
log.Println("STDERR:")
8281
fmt.Fprintln(os.Stderr, stderr)
8382
}
84-
return errors.FormatAPIError(err)
83+
return err
8584
}

cli/cmd/git_pull.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func AddGitPullCmd(git *cobra.Command, opts GlobalOptions) {
5151
Long: io.Long(`Pull latest changes from the remote git repository.
5252
5353
if specified, pulls a specific branch.`),
54-
Example: io.FormatExampleCommands("pull", []io.Example{
54+
Example: io.FormatExampleCommands("git pull", []io.Example{
5555
{Cmd: "", Desc: "Pull latest HEAD from current branch"},
5656
{Cmd: "--remote origin --branch staging", Desc: "Pull branch staging from remote origin"},
5757
}),

docs/cs_git_pull.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ cs git pull [flags]
1616

1717
```
1818
# Pull latest HEAD from current branch
19-
$ cs pull
19+
$ cs git pull
2020
2121
# Pull branch staging from remote origin
22-
$ cs pull --remote origin --branch staging
22+
$ cs git pull --remote origin --branch staging
2323
```
2424

2525
### Options

0 commit comments

Comments
 (0)