Skip to content

Commit 5361bab

Browse files
easyCZroboquat
authored andcommitted
[public-api] Convert server errors to appropriate RPC errors
1 parent 5fadd8a commit 5361bab

File tree

4 files changed

+119
-43
lines changed

4 files changed

+119
-43
lines changed

components/public-api-server/pkg/apiv1/workspace.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"context"
99
"github.com/gitpod-io/gitpod/public-api-server/pkg/proxy"
1010
v1 "github.com/gitpod-io/gitpod/public-api/v1"
11+
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus/ctxlogrus"
1112
"google.golang.org/grpc/codes"
1213
"google.golang.org/grpc/metadata"
1314
"google.golang.org/grpc/status"
@@ -27,19 +28,30 @@ type WorkspaceService struct {
2728
}
2829

2930
func (w *WorkspaceService) GetWorkspace(ctx context.Context, r *v1.GetWorkspaceRequest) (*v1.GetWorkspaceResponse, error) {
31+
logger := ctxlogrus.Extract(ctx)
3032
token, err := bearerTokenFromContext(ctx)
3133
if err != nil {
3234
return nil, err
3335
}
3436

3537
server, err := w.connectionPool.Get(ctx, token)
3638
if err != nil {
39+
logger.WithError(err).Error("Failed to get connection to server.")
3740
return nil, status.Error(codes.Internal, "failed to establish connection to downstream services")
3841
}
3942

4043
workspace, err := server.GetWorkspace(ctx, r.GetWorkspaceId())
4144
if err != nil {
42-
return nil, status.Error(codes.NotFound, "failed to get workspace")
45+
logger.WithError(err).Error("Failed to get workspace.")
46+
converted := proxy.ConvertError(err)
47+
switch status.Code(converted) {
48+
case codes.PermissionDenied:
49+
return nil, status.Error(codes.PermissionDenied, "insufficient permission to access workspace")
50+
case codes.NotFound:
51+
return nil, status.Error(codes.NotFound, "workspace does not exist")
52+
default:
53+
return nil, status.Error(codes.Internal, "unable to retrieve workspace")
54+
}
4355
}
4456

4557
return &v1.GetWorkspaceResponse{

components/public-api-server/pkg/apiv1/workspace_test.go

Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package apiv1
66

77
import (
88
"context"
9-
"fmt"
9+
"errors"
1010
"github.com/gitpod-io/gitpod/common-go/baseserver"
1111
gitpod "github.com/gitpod-io/gitpod/gitpod-protocol"
1212
v1 "github.com/gitpod-io/gitpod/public-api/v1"
@@ -37,24 +37,10 @@ func TestWorkspaceService_GetWorkspace(t *testing.T) {
3737
foundWorkspaceID: {
3838
LatestInstance: &gitpod.WorkspaceInstance{},
3939
Workspace: &gitpod.Workspace{
40-
BaseImageNameResolved: "",
41-
BasedOnPrebuildID: "",
42-
BasedOnSnapshotID: "",
43-
Config: nil,
44-
ContentDeletedTime: "",
45-
Context: nil,
46-
ContextURL: contextURL,
47-
CreationTime: "",
48-
Deleted: false,
49-
Description: description,
50-
ID: foundWorkspaceID,
51-
ImageNameResolved: "",
52-
ImageSource: nil,
53-
OwnerID: ownerID,
54-
Pinned: false,
55-
Shareable: false,
56-
SoftDeleted: "",
57-
Type: "",
40+
ContextURL: contextURL,
41+
Description: description,
42+
ID: foundWorkspaceID,
43+
OwnerID: ownerID,
5844
},
5945
},
6046
}},
@@ -68,39 +54,44 @@ func TestWorkspaceService_GetWorkspace(t *testing.T) {
6854
client := v1.NewWorkspacesServiceClient(conn)
6955
ctx := metadata.AppendToOutgoingContext(context.Background(), "authorization", bearerToken)
7056

71-
scenarios := []struct {
72-
name string
57+
type Expectation struct {
58+
Code codes.Code
59+
Response *v1.GetWorkspaceResponse
60+
}
7361

62+
scenarios := []struct {
63+
name string
7464
WorkspaceID string
75-
76-
ErrorCode codes.Code
77-
Response *v1.GetWorkspaceResponse
65+
Expect Expectation
7866
}{
7967
{
8068
name: "returns a workspace when workspace is found by ID",
8169
WorkspaceID: foundWorkspaceID,
82-
ErrorCode: codes.OK,
83-
Response: &v1.GetWorkspaceResponse{
84-
Result: &v1.Workspace{
85-
WorkspaceId: foundWorkspaceID,
86-
OwnerId: ownerID,
87-
ProjectId: "",
88-
Context: &v1.WorkspaceContext{
89-
ContextUrl: contextURL,
90-
Details: &v1.WorkspaceContext_Git_{Git: &v1.WorkspaceContext_Git{
91-
NormalizedContextUrl: contextURL,
92-
Commit: "",
93-
}},
70+
Expect: Expectation{
71+
Code: codes.OK,
72+
Response: &v1.GetWorkspaceResponse{
73+
Result: &v1.Workspace{
74+
WorkspaceId: foundWorkspaceID,
75+
OwnerId: ownerID,
76+
ProjectId: "",
77+
Context: &v1.WorkspaceContext{
78+
ContextUrl: contextURL,
79+
Details: &v1.WorkspaceContext_Git_{Git: &v1.WorkspaceContext_Git{
80+
NormalizedContextUrl: contextURL,
81+
Commit: "",
82+
}},
83+
},
84+
Description: description,
9485
},
95-
Description: description,
9686
},
9787
},
9888
},
9989
{
10090
name: "not found when workspace is not found by ID",
10191
WorkspaceID: "some-not-found-workspace-id",
102-
ErrorCode: codes.NotFound,
103-
Response: nil,
92+
Expect: Expectation{
93+
Code: codes.NotFound,
94+
},
10495
},
10596
}
10697

@@ -109,8 +100,10 @@ func TestWorkspaceService_GetWorkspace(t *testing.T) {
109100
resp, err := client.GetWorkspace(ctx, &v1.GetWorkspaceRequest{
110101
WorkspaceId: scenario.WorkspaceID,
111102
})
112-
require.Equal(t, scenario.ErrorCode, status.Code(err), "status code must match")
113-
if diff := cmp.Diff(scenario.Response, resp, protocmp.Transform()); diff != "" {
103+
if diff := cmp.Diff(scenario.Expect, Expectation{
104+
Code: status.Code(err),
105+
Response: resp,
106+
}, protocmp.Transform()); diff != "" {
114107
t.Errorf("unexpected difference:\n%v", diff)
115108
}
116109
})
@@ -153,7 +146,7 @@ type FakeGitpodAPI struct {
153146
func (f *FakeGitpodAPI) GetWorkspace(ctx context.Context, id string) (res *gitpod.WorkspaceInfo, err error) {
154147
w, ok := f.workspaces[id]
155148
if !ok {
156-
return nil, fmt.Errorf("workspace not found")
149+
return nil, errors.New("code 404")
157150
}
158151

159152
return w, nil
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package proxy
6+
7+
import (
8+
"google.golang.org/grpc/codes"
9+
"google.golang.org/grpc/status"
10+
"strings"
11+
)
12+
13+
func ConvertError(err error) error {
14+
if err == nil {
15+
return nil
16+
}
17+
18+
s := err.Error()
19+
20+
if strings.Contains(s, "code 401") {
21+
return status.Error(codes.PermissionDenied, s)
22+
}
23+
24+
// components/gitpod-protocol/src/messaging/error.ts
25+
if strings.Contains(s, "code 404") {
26+
return status.Error(codes.NotFound, s)
27+
}
28+
29+
// components/gitpod-messagebus/src/jsonrpc-server.ts#47
30+
if strings.Contains(s, "code -32603") {
31+
return status.Error(codes.Internal, s)
32+
}
33+
34+
return status.Error(codes.Internal, s)
35+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package proxy
6+
7+
import (
8+
"errors"
9+
"github.com/stretchr/testify/require"
10+
"google.golang.org/grpc/codes"
11+
"google.golang.org/grpc/status"
12+
"testing"
13+
)
14+
15+
func TestConvertError(t *testing.T) {
16+
scenarios := []struct {
17+
WebsocketError error
18+
ExpectedStatus codes.Code
19+
}{
20+
{
21+
WebsocketError: errors.New("reconnecting-ws: bad handshake: code 401 - URL: wss://main.preview.gitpod-dev.com/api/v1 - headers: map[Authorization:[Bearer foo] Origin:[http://main.preview.gitpod-dev.com/]]"),
22+
ExpectedStatus: codes.PermissionDenied,
23+
},
24+
{
25+
WebsocketError: errors.New("jsonrpc2: code -32603 message: Request getWorkspace failed with message: No workspace with id 'some-id' found."),
26+
ExpectedStatus: codes.Internal,
27+
},
28+
}
29+
30+
for _, s := range scenarios {
31+
converted := ConvertError(s.WebsocketError)
32+
require.Equal(t, s.ExpectedStatus, status.Code(converted))
33+
// the error message should remain the same
34+
require.Equal(t, s.WebsocketError.Error(), status.Convert(converted).Message())
35+
}
36+
}

0 commit comments

Comments
 (0)